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()) && ...) { ... }
, 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, , .