Search code examples
wpflinqienumerableobservablecollectionieditableobject

Why does Linq.Enumerable.Where break my ObservableCollection


Background:

I am writing a WPF application, strictly following the MVVM pattern. I have a BaseRepository class as a generic interface for connecting to different databases (EF is not an option), and everything works fine; this is just a technical question.

I use a wrapped ObservableCollection called NotifyingCollection, to subscribe to IEditableObject's ItemEndEdit events (my ViewModelBase entity wrapper implements INotifyPropertyChanged and IEditableObject members).

The provided code sample throws an "'EditItem' is not allowed for this view" exception when the ReadAll method is called upon editing an item in my WPF DataGrid. However, if I replace the line in the method with the commented out part, it works perfectly!

Question:

In other words, it looks like relaying the Linq.Enumerable.Where extension method instead of returning an IEnumerable version of the collection removes functionality from the custom collection; why would that happen if they are both IEnumerable?

Code Sample:

namespace MyCompany.Common.Abstracts
{
    using System;
    using System.Collections.Generic;
    using System.Collections.Specialized;
    using System.ComponentModel;
    using System.Data.Common;
    using System.Diagnostics;
    using System.Linq;
    using System.Linq.Expressions;
    using MyCompany.Common.Extensions;
    using MyCompany.Common.Utilities;


    public abstract class BaseRepository<TEntity> : IDisposable where TEntity : ViewModelBase
    {
        protected BaseRepository()
        {
            this.EntitySet = new NotifyingCollection<TEntity>();
            this.EntitySet.ItemEndEdit += new ViewModelBase.ItemEndEditEventHandler(ItemEndEdit);
            this.EntitySet.CollectionChanged += new NotifyCollectionChangedEventHandler(CollectionChanged);
        }

        protected abstract NotifyingCollection<TEntity> EntitySet { get; set; }

        protected virtual void PropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            Debug.WriteLine(String.Format("Modify '{0}'", e.PropertyName), "PropertyChanged");
        }

        protected virtual void ItemEndEdit(IEditableObject sender)
        {
            this.Update(sender as TEntity);
        }

        protected virtual void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            var collection = (e.Action == NotifyCollectionChangedAction.Remove) ?
                e.OldItems : e.NewItems;

            foreach (TEntity entity in collection)
            {
                switch (e.Action)
                {
                    case NotifyCollectionChangedAction.Add:
                        entity.PropertyChanged += this.PropertyChanged;
                        this.Create(entity);
                        break;

                    case NotifyCollectionChangedAction.Remove:
                        entity.PropertyChanged -= this.PropertyChanged;
                        this.Delete(entity);
                        break;

                    default:
                        Debug.WriteLine(String.Format("{0} '{1}'", e.Action.ToString(), entity.DisplayName), "CollectionChanged");
                        break;
                }
            }
        }

        public virtual bool Create(TEntity entity)
        {
            Debug.WriteLine(String.Format("Create '{0}'", entity.DisplayName), "CollectionChanged");
            return true;
        }

        public virtual IEnumerable<TEntity> Read(Expression<Func<TEntity, bool>> filter)
        {
            return this.EntitySet.Where(filter.Compile());
        }

        public virtual IEnumerable<TEntity> ReadAll()
        {
            return this.Read(x => true);
            //return this.EntitySet;
        }

        public virtual bool Update(TEntity entity)
        {
            Debug.WriteLine(String.Format("Update '{0}'", entity.DisplayName), "ItemEndEdit");
            return true;
        }

        public virtual bool Delete(TEntity entity)
        {
            Debug.WriteLine(String.Format("Delete '{0}'", entity.DisplayName), "CollectionChanged");
            return true;
        }

        public virtual IEnumerable<TColumn> GetColumn<TColumn>(string columnName)
        {
            var lookupTable = this.Read(x => x != null);

            List<TColumn> column = new List<TColumn>();
            foreach (TEntity record in lookupTable)
            {
                column.Add(record.GetPropValue<TColumn>(columnName));
            }
            var result = column.Distinct();

            foreach (TColumn element in result)
            {
                yield return element;
            }
        }

        public abstract int Commit();

        public abstract DbTransaction BeginTransaction();

        public abstract void Dispose();
    }
}

Solution

  • From MichaelLPerry's blog: Common mistakes while using ObservableCollection

    ObservableCollection is imperative. Linq is declarative. The two cannot be used together without extra help.

    Imperative code explicitly acts upon something. When using an ObservableCollection, you explicitly call Add, Remove, and other methods to change the collection. You have to decide exactly when and how to take these actions.

    Declarative code implicitly generates something. When using linq, you declare what a collection will look like, how it will be filtered, mapped, and transformed. You don’t explicitly modify the collection.

    These two paradigms don’t mix. Once you use linq on an ObservableCollection, it is no longer observable. There are open-source projects like Bindable Linq that bridge the gap. But without that extra help, people are often surprised when things don’t work.