Polymorphic removal of objects added to ObjectSet<TEntity> will NOT raise the IBindingList.ListChanged on ObjectSet<TEntity>.IListSource.GetList()

耗尽温柔 提交于 2019-12-10 16:16:28

问题


OVERVIEW/DESCRIPTION

Simple: Polymorphic removal of objects of runtime types derived from TEntity added to an ObjectSet<TEntity> will NOT raise the IBindingList.ListChanged event on the IBindingList object returned by the of the ObjectSet<TEntity>.IListSource.GetList() method.

However, removal of instances whose runtime type match TEntity are effectively notified on the ListChanged event.

To clarify, at all times, objects are effectively removed from the underlying collections or data views/stores, but when these objects are instances of types strictly derived from the actual TEntity used, the ListChanged event is not raised to notify of their removal.

This is simply a phenomenal BUG for the purposes of appropriate data-binding support of runtime polymorphism for collections.

REPLICATION

Model Setup

  1. Table per Type Strategy.
  2. Entity model mapped and validated against gemerated SQL database on Server 2012 Express.

This is the entity hierarchy (pseudo-UML):

FiascoEntityContext : ObjectContext
  + Foos : ObjectSet<Foo>

Foo : EntityObject
  + Id: Int32
  + Name: String

SpecialFoo : Foo
  + SpecialProperty: String

Demonstration Code

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using System.Data.Objects;

namespace FiascoEF {
    class Program {
        static void Main(string[] args) {
            using (FiascoEntityContext context = new FiascoEntityContext()) {
                //
                // add some foos
                //
                context.Foos.AddObject(new Foo { Name = "Foo1" });
                context.Foos.AddObject(new BetterFoo { Name = "BetterFoo1", SpecialProperty = "Something Special" });
                context.SaveChanges();
                //
                // show the contents
                //
                Console.WriteLine("Listing all foos:");
                foreach (var foo in context.Foos) {
                    Console.WriteLine("     Got {0}: Id={1} Name={2} (State={3})", foo, foo.Id, foo.Name, foo.EntityState);
                }
                //
                // attach handler for the ListChanged event of the IBindingList returned by context.Foos as IListSource
                // NOTE: have to do this here bacause SaveChanges() above will reset the internal IBindingList
                //
                var bindingList = (context.Foos as IListSource).GetList() as IBindingList;
                bindingList.ListChanged += new ListChangedEventHandler(bindingList_ListChanged);
                //
                // delete all foos and show state. expect the event handler above to be invoked.
                //
                Console.WriteLine("Deleting all foos:");
                foreach (var foo in context.Foos) {
                    context.Foos.DeleteObject(foo);
                    Console.WriteLine("     Deleted {0}: Id={1} Name={2} (State={3})", foo, foo.Id, foo.Name, foo.EntityState);
                }
                context.SaveChanges();
            }
        }

        static void bindingList_ListChanged(object sender, ListChangedEventArgs e) {
            Console.WriteLine("     Event on {0}: {1}", sender, e.ListChangedType);
        }
    }
}

Expected Results

Listing all foos:
     Got FiascoEF.Foo: Id=257 Name=Foo1 (State=Unchanged)
     Got FiascoEF.BetterFoo: Id=258 Name=BetterFoo1 (State=Unchanged)
Deleting all foos:
     Event on System.Data.Objects.ObjectView`1[FiascoEF.Foo]: ItemDeleted
     Deleted FiascoEF.Foo: Id=257 Name=Foo1 (State=Deleted)
     Event on System.Data.Objects.ObjectView`1[FiascoEF.Foo]: ItemDeleted
     Deleted FiascoEF.BetterFoo: Id=258 Name=BetterFoo1 (State=Deleted)

Actual Results

Listing all foos:
     Got FiascoEF.Foo: Id=257 Name=Foo1 (State=Unchanged)
     Got FiascoEF.BetterFoo: Id=258 Name=BetterFoo1 (State=Unchanged)
Deleting all foos:
     Event on System.Data.Objects.ObjectView`1[FiascoEF.Foo]: ItemDeleted
     Deleted FiascoEF.Foo: Id=257 Name=Foo1 (State=Deleted)
     Deleted FiascoEF.BetterFoo: Id=258 Name=BetterFoo1 (State=Deleted)

RESEARCH

Through reflector found that the actual IBindingList returned is of type ObjectView<TElement>, and this type delegates removal operations to an internal IObjectViewData<TElement>. The implementation found for that interface is ObjectViewQueryResultData<TElement> which defines:

public ListChangedEventArgs OnCollectionChanged(object sender, CollectionChangeEventArgs e, ObjectViewListener listener) {

    ListChangedEventArgs changeArgs = null;

    if (e.Element.GetType().IsAssignableFrom(typeof(TElement)) && _bindingList.Contains((TElement) (e.Element))) {
        ...
        changeArgs = new ListChangedEventArgs(ListChangedType.ItemDeleted, ...);
        ...
    }

    return changeArgs;
}

The check:

if (e.Element.GetType().IsAssignableFrom(typeof(TElement)) && ...) { ... }

seems bogus... probably the following was intended?

if (typeof(TElement).IsAssignableFrom(e.Element.GetType()) && ...) { ... }

回答1:


Fair enough, bug reported to Microsoft — http://connect.microsoft.com/VisualStudio/feedback/details/749368... They seem to have acknowledged the issue, but it's not clear what they will do.

Remember, we're talking about the IBindingList implementation retrieved by the ObjectSet<T> when viewed as a IListSource for the purposes of data-binding, so the event is expected to be fired, just as it does for the case of a homogeneous list.

I broke loose by defining a class that inherits from ObservableCollection<T> and wraps the ObjectSet<T>, then implement IListSource with the help of the DbExtensions.ToBindingList<T>(this ObservableCollection<T>) extension method.

Alternatively, I could just move on and start using the DbContext API entirely, but defining my own ObservableCollection<T> allows me to keep using ObjectContext for now, which is what I want.



来源:https://stackoverflow.com/questions/11061548/polymorphic-removal-of-objects-added-to-objectsettentity-will-not-raise-the-ib

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!