简体   繁体   中英

Perform async search that returns ObservableCollection

I'm using Visual Studio 2015 and Entity Framework 6 to build an MVVM Light WPF app. When the user clicks the Search button, it calls a RelayCommand which has been defined like this in the View Model's constructor:

SearchEmployeesRelayCommand = new RelayCommand(SearchEmployees);

The SearchEmployees method in the View Model looks like this:

private BackgroundWorker _worker;

public void SearchEmployees()
{
    _worker = new BackgroundWorker(); // use this to show busy indicator

    var dataService = new EmployeeDataService();
    _worker.DoWork += (o, ea) =>
    {
        SearchResults = dataService.SearchEmployees(SelectedColumn, SearchValue);
    };
    _worker.RunWorkerCompleted += (o, ea) =>
    {
        IsSearching = false;
    };

    IsSearching = true;
    _worker.RunWorkerAsync();
}

The data service's search method looks like this:

public ObservableCollection<EmployeeViewModel> 
    SearchEmployees(string selectedColumn, string searchValue)
{
    var paramEmployee = Expression.Parameter(typeof(Employee), "e");

    var comparison = Expression.Lambda<Func<Employee, bool>>(
        Expression.Equal(
            Expression.Property(paramEmployee, selectedColumn),
            Expression.Constant(searchValue)),
            paramEmployee).Compile();

    using (var context = new MyEntities())
    {
        var query = (from e in context.Employees
                     .Where(comparison)
                     select new EmployeeViewModel
                     {
                         // Various EF model properties...
                     });
        return new ObservableCollection<EmployeeViewModel>(query);
    }
}

If I try to make the above method async and awaitable , with something like this:

return await new ObservableCollection<EmployeeViewModel>(query);

It gives this error:

'ObservableCollection' does not contain a definition for 'GetAwaiter' and no extension method 'GetAwaiter' accepting a first argument of type 'ObservableCollection' could be found (are you missing a using directive or an assembly reference?)

How do you make the search async if it's returning an ObservableCollection ? Thanks.

Update : For the busy indicator to work, I had to make this change:

_worker.DoWork += async (o, ea) =>
{
    SearchResults = await dataService
        .SearchEmployees(selectedColumnValue, SearchValue);
    IsSearching = false;
};

And I removed the _worker.RunWorkerCompleted block altogether. There probably is a better way to do that, but this was how I got it working.

There are a couple of approaches. First, you can keep your database access synchronous and just run it on a background thread. Note that Task.Run is a modern replacement for BackgroundWorker (I have a blog series that draws parallels between the two ):

public async Task SearchEmployeesAsync()
{
  var dataService = new EmployeeDataService();
  var selectedColumn = SelectedColumn;
  var searchValue = searchValue;

  IsSearching = true;
  try
  {
    SearchResults = await Task.Run(() => dataService.SearchEmployees(selectedColumn, searchValue));
  }
  finally
  {
    IsSearching = false;
  }
}

Alternatively, since you are using EF6, you can make your database query asynchronous and not mess around with background threads at all:

public async Task<ObservableCollection<EmployeeViewModel>> 
    SearchEmployeesAsync(string selectedColumn, string searchValue)
{
  var paramEmployee = Expression.Parameter(typeof(Employee), "e");
  var comparison = Expression.Lambda<Func<Employee, bool>>(
    Expression.Equal(
      Expression.Property(paramEmployee, selectedColumn),
      Expression.Constant(searchValue)),
      paramEmployee).Compile();

  using (var context = new MyEntities())
  {
    var query = (from e in context.Employees
                 .Where(comparison)
                 select new EmployeeViewModel
                 {
                     // Various EF model properties...
                 });
    var data = await query.ToListAsync();
    return new ObservableCollection<EmployeeViewModel>(data);
  }
}

public async Task SearchEmployeesAsync()
{
  var dataService = new EmployeeDataService();
  IsSearching = true;
  try
  {
    SearchResults = await dataService.SearchEmployeesAsync(SelectedColumn, SearchValue);
  }
  finally
  {
    IsSearching = false;
  }
}

You should not make BackgroundWorker.DoWork asynchronous; that will cause it to "end early" and prevent it from gracefully handling exceptions. BGW simply wasn't designed to work with async code.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM