Полиморфное удаление объектов, добавленных в ObjectSet‹TEntity›, НЕ вызовет IBindingList.ListChanged в ObjectSet‹TEntity›.IListSource.GetList()

ОБЗОР/ОПИСАНИЕ

Просто: полиморфное удаление объектов типов среды выполнения, производных от TEntity, добавленных к ObjectSet<TEntity>, НЕ вызовет событие IBindingList.ListChanged для объекта IBindingList, возвращаемого методом ObjectSet<TEntity>.IListSource.GetList().

Однако удаление экземпляров, тип среды выполнения которых соответствует TEntity, эффективно уведомляется о событии ListChanged.

Чтобы уточнить, в любое время объекты эффективно удаляются из базовых коллекций или представлений/хранилищ данных, но когда эти объекты являются экземплярами типов, строго производных от фактического используемого TEntity, событие ListChanged не возникает, чтобы уведомить об их удалении.

Это просто феноменальная ОШИБКА для целей соответствующей поддержки привязки данных полиморфизма времени выполнения для коллекций.

РЕПЛИКАЦИЯ

Настройка модели

  1. Таблица для стратегии типа.
  2. Модель объекта сопоставляется и проверяется на основе генерированной базы данных SQL на сервере Server 2012 Express.

Это иерархия сущностей (псевдо-UML):

FiascoEntityContext : ObjectContext
  + Foos : ObjectSet<Foo>

Foo : EntityObject
  + Id: Int32
  + Name: String

SpecialFoo : Foo
  + SpecialProperty: String

Демонстрационный код

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);
        }
    }
}

Ожидаемые результаты

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)

Фактические результаты

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)

ИССЛЕДОВАНИЯ

Через рефлектор обнаружил, что фактическое возвращаемое IBindingList имеет тип ObjectView<TElement>, и этот тип делегирует операции удаления внутреннему IObjectViewData<TElement>. Для этого интерфейса найдена реализация ObjectViewQueryResultData<TElement>, которая определяет:

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;
}

Чек:

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

кажется фальшивкой... вероятно, имелось в виду следующее?

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

person Fernando Espinosa    schedule 16.06.2012    source источник
comment
Какой вопрос? Если вы пытаетесь сообщить об ошибке, я бы посоветовал сообщить об этом в Microsoft, а не здесь, в SO.   -  person Chris Shain    schedule 17.06.2012
comment
Честно говоря, сообщение об ошибке — connect.microsoft.com/VisualStudio/feedback/details/749368   -  person Fernando Espinosa    schedule 18.06.2012


Ответы (1)


Справедливо, сообщение об ошибке отправлено в Microsoft — http://connect.microsoft.com/VisualStudio/feedback/details/749368... Кажется, они признали проблему, но неясно, что они будут делать.

Помните, мы говорим о реализации IBindingList, извлекаемой ObjectSet<T> при просмотре как IListSource для целей привязки данных, поэтому ожидается, что событие будет запущено, как и в случае однородного списка.

Я вырвался, определив класс, который наследуется от ObservableCollection<T> и обертывает ObjectSet<T>, а затем реализует IListSource с помощью метода расширения DbExtensions.ToBindingList<T>(this ObservableCollection<T>).

В качестве альтернативы я мог бы просто двигаться дальше и полностью начать использовать DbContext API, но определение собственного ObservableCollection<T> позволяет мне пока продолжать использовать ObjectContext, чего я и хочу.

person Fernando Espinosa    schedule 19.07.2012