Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Re-evaluate LINQ query when ObservableCollection changes

I have a common issue that I'd like to (hopefully) find a better solution for moving forward. I have an ObservableCollection containing a master list of data. In my client code I need to 'transform' the data into a new form for display to the user. I use a LINQ statement like:

var newList = (from item in observable
               select new { FirstInitial = item.Name[0] });

I know it's pretty rudimentary but it is enough to demonstrate the problem. (Notice the projection, this is not a simple filter or grouping statement.) I then display newList in my UI via data-binding.

Problem is, when the original collection changes, the UI doesn't. The solution I've applied thus far has been to attach an event handler to the original collection's CollectionChanged event which re-evaluates the query and updates the bound property.

While this works fine, it's a lot of repeated code every time I run across this scenario. Is there a way that I can have the LINQ query return an ObservableCollection that "automatically" updates when the source Observable is changed?

In other words, I'd like to implement some 'canned' functionality so I can simply reuse it whenever I have this scenario.

UPDATE

Thanks to Scroog1 for helping me see my original post was too coupled to the UI for what I was really asking. Take the following example as a better description of the problem:

public class SomeClass
{
    private ObservableCollection<Employee> _allEmployees;
    private ObservableCollection<Employee> _currentEmployees;

    public ObservableCollection<Employee> CurrentEmployees
    {
        get
        {
            if (_currentEmployees == null)
                _currentEmployees = _allEmployees.Where(e => !e.IsTerminated);

            return _currentEmployees;
        }
    }
}

public class SomeViewModel
{
    private ICollectionView _view;

    public ICollectionView CurrentView
    {
        if (_view == null)
        {
            var cvs = new CollectionViewSource()
            {
                Source = someClass.CurrentEmployees
            }

            cvs.Add(new SortDescription("Name", ListSortDirection.Ascending));

            _view = cvs.View;
        }

        return _view;
    }
}

As you can see, the code where the query exists is not what is directly bound to the UI. I use this example to demonstrate that I am asking from a more general use-case than strictly a UI data-binding scenario.

like image 365
SonOfPirate Avatar asked Nov 05 '22 00:11

SonOfPirate


1 Answers

I would do something like this (assuming an ObservableCollection called observable and class implementing INotifyPropertyChanged with RaisePropertyChanged method):

public IEnumerable<string> NewList
{
    return from item in observable
           select item.Name;
}

observable.CollectionChanged += delegate { RaisePropertyChanged("NewList"); };

Then the when observable is changed, the UI will be told that NewList has changed to and re-evaluate the query.

For multiple dependent items you can do:

observable.CollectionChanged += delegate
    {
        RaisePropertyChanged("NewList",
                             "OtherProperty",
                             "YetAnotherProperty",
                             "Etc");
    };

Update

The above works fine in general for properties, as you will get the latest value every time you access it and INPC can be used to tell things to re-read it.

For the slightly more interesting case of collections, I would implement a custom class that implements INotifyCollectionChanged and IEnumerable and wraps the LINQ. E.g.,

public class CustomObservableCollection<T> : INotifyCollectionChanged,
                                             INotifyPropertyChanged,
                                             IEnumerable<T>
{
    private readonly IEnumerable<T> _collection;
    public CustomObservableCollection(IEnumerable<T> collection)
    {
        _collection = collection;
    }
    public IEnumerator<T> GetEnumerator()
    {
        _collection.GetEnumerator();
    }
    public void RaiseCollectionChanged() { ... }
    ...
}

Then you can do:

var newList = new CustomObservableCollection(from item in observable
                                             select item.Name);
observable.CollectionChanged += delegate { newList.RaiseCollectionChanged(); };

Update 2

You could even pass the dependency to CustomObservableCollection:

public class CustomObservableCollection<T> : INotifyCollectionChanged,
                                             INotifyPropertyChanged,
                                             IEnumerable<T>
{
    private readonly IEnumerable<T> _collection;
    public CustomObservableCollection(IEnumerable<T> collection,
                 params ObservableCollection[] dependencies)
    {
        _collection = collection;
        foreach (var dep in dependencies)
            dep.CollectionChanged += RaiseCollectionChanged();
    }
    public IEnumerator<T> GetEnumerator()
    {
        _collection.GetEnumerator();
    }
    public void RaiseCollectionChanged() { ... }
    ...
}
like image 138
Scroog1 Avatar answered Nov 15 '22 05:11

Scroog1