Polymorphic deletion of objects added to an ObjectSet <TEntity> will NOT raise IBindingList.ListChanged by ObjectSet <TEntity> .IListSource.GetList ()

OVERVIEW / DESCRIPTION

Simple: Polymorphic deletion of objects of execution types obtained from TEntity, added to ObjectSet<TEntity>, will not result in the event of the IBindingList.ListChangedobject IBindingListreturned ObjectSet<TEntity>.IListSource.GetList().

However, deleting instances whose execution type matches TEntity is effectively notified of the event ListChanged.

To clarify, objects are always deleted from base collections or data views / storages, but when these objects are instances of types strictly derived from the actual TEntity, the event ListChangeddoes not occur to notify about their removal.

This is simply a phenomenal BUG for the purpose of appropriately supporting run-time polymorphism data binding for collections.

REPLICATION

Model customization

  • Table for type strategy.
  • An entity model mapped and validated based on a gemerated SQL database on a 2012 Express server.

This is an entity hierarchy (pseudo-UML):

FiascoEntityContext : ObjectContext
  + Foos : ObjectSet<Foo>

Foo : EntityObject
  + Id: Int32
  + Name: String

SpecialFoo : Foo
  + SpecialProperty: String

Demo 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 the reflector, I discovered that the actual IBindingListtype was returned ObjectView<TElement>, and this type delegates the delete operation to the internal one IObjectViewData<TElement>. The implementation found for this 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;
}

Verification:

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

seems fictitious ... maybe the following was intended?

if (typeof(TElement).IsAssignableFrom(e.Element.GetType()) && ...) { ... }
+4
source share
1 answer

, Microsoft - http://connect.microsoft.com/VisualStudio/feedback/details/749368... , , , .

, IBindingList, ObjectSet<T>, IListSource , , , .

, , ObservableCollection<T> ObjectSet<T>, IListSource DbExtensions.ToBindingList<T>(this ObservableCollection<T>).

API DbContext, ObservableCollection<T> ObjectContext, , .

+1

All Articles