Entity Framework 6 - используйте мой getHashCode()

Для этого нужно определенное количество фона, пожалуйста, несите меня!

У нас есть приложение WPF n-уровня с использованием EF - мы загружаем данные из базы данных через dbContext в классы POCO. DbContext уничтожается, и пользователь может редактировать данные. Мы используем "государственную живопись", предложенную Джули Лерман в ее книге "Programming Entity Framework: DBContext", поэтому, когда мы добавляем корневой объект в новый dbContext для сохранения, мы можем установить, будет ли каждый дочерний объект добавлен, изменен или оставлен без изменений и т.д..

Проблема, которую мы имели, когда мы впервые это сделали (еще в ноябре 2012 года!), заключалась в том, что если корневой объект, который мы добавляем в dbContext, имеет несколько экземпляров одного и того же дочернего объекта (т.е. запись "Задача", связанная с пользователь с "Историей состояний" также связан с одним и тем же пользователем), процесс завершится неудачно, потому что хотя дочерние сущности были одинаковыми (из одной строки базы данных), им были присвоены разные хэш-коды, поэтому EF распознал их как разные объекты.

Мы исправили это (еще в декабре 2012 года!), переопределив GetHashCode на наших объектах, чтобы вернуть либо идентификатор базы данных, если сущность получена из базы данных, либо уникальный отрицательный номер, если объект еще не сохранен. Теперь, когда мы добавляем корневой объект в dbContext, он был достаточно умен, чтобы реализовать один и тот же дочерний объект, добавляемый несколько раз, и он правильно его рассматривал. Это работает отлично с декабря 2012 года, пока мы не перешли на EF6 на прошлой неделе...

Одна из новых "функций" с EF6 заключается в том, что теперь она использует собственные методы Equals и GetHashCode для выполнения задач отслеживания изменений, игнорируя любые пользовательские переопределения. Смотрите: http://msdn.microsoft.com/en-us/magazine/dn532202.aspx (найдите "Меньше вмешательства в стиль кодирования" ). Это здорово, если вы ожидаете, что EF будет управлять отслеживанием изменений, но в отключенном n-уровневом приложении мы этого не хотим, и на самом деле это нарушает наш код, который работает отлично уже более года.

Надеюсь, это имеет смысл.

Теперь - вопрос - кто-нибудь знает, каким образом мы можем сказать, что EF6 использует методы GetHashCode и Equals, как это было в EF5, или у кого-то есть лучший способ справиться с добавлением корневого объекта в dbContext, который имеет дублированные дочерние объекты в нем, чтобы EF6 был доволен им?

Спасибо за любую помощь. Извините за длинный пост.

ОБНОВЛЕНО Пробив код EF, он выглядит как хэш-код объекта InternalEntityEntry (dbEntityEntry), который был установлен путем получения хэш-кода объекта, но теперь в EF6 извлекается с помощью RuntimeHelpers.GetHashCode(_entity), что означает наш переопределенный хэш-код на объект игнорируется. Поэтому я предполагаю, что использование EF6 для использования нашего хэш-кода не может быть и речи, поэтому, возможно, мне нужно сосредоточиться на том, как добавить объект в контекст, который потенциально дублирует дочерние объекты без нарушения EF. Любые предложения?

ОБНОВЛЕНИЕ 2 Самое неприятное, что это изменение в функциональности сообщается как хорошая вещь, а не, как я вижу, нарушение! Конечно, если у вас отключены сущности, и вы загрузили их с помощью .AsNoTracking() для производительности (и потому, что мы знаем, что мы собираемся отключить их, поэтому зачем их отслеживать), тогда нет причины, чтобы dbContext переопределял наш метод getHashcode!

ОБНОВЛЕНИЕ 3 Спасибо за все комментарии и предложения - очень благодарен! После некоторых экспериментов он, по-видимому, связан с .AsNoTracking(). Если вы загружаете данные с помощью .AsNoTracking(), дубликаты дочерних объектов являются отдельными объектами в памяти (с разными хэш-кодами), поэтому существует краска состояния проблемы и сохранение их позже. Ранее мы исправляли эту проблему, переопределяя хэш-коды, поэтому, когда сущности добавляются обратно в контекст сохранения, повторяющиеся объекты распознаются как один и тот же объект и добавляются только один раз, но мы больше не можем это делать с EF6. Итак, теперь мне нужно исследовать далее, почему мы использовали .AsNoTracking() в первую очередь. Еще одна моя мысль заключается в том, что, возможно, EF6 change tracker должен использовать собственный метод генерации хэш-кода для записей, которые он активно отслеживает, - если объекты были загружены с помощью .AsNoTracking(), возможно, вместо этого он должен использовать хэш-код из основного объекта?

ОБНОВЛЕНИЕ 4Итак, теперь мы убедились, что мы не можем продолжать использовать наш подход (переопределенные хэш-коды и .AsNoTracking) в EF6, как мы должны управлять обновлениями отключенных объектов? Я создал этот простой пример с blogposts/comments/authors: Diagram and Data

В этом примере я хочу открыть blogpost 1, изменить контент и автора и снова сохранить. Я пробовал 3 подхода с EF6, и я не могу заставить его работать:

BlogPost blogpost;

using (TestEntities te = new TestEntities())
{
    te.Configuration.ProxyCreationEnabled = false;
    te.Configuration.LazyLoadingEnabled = false;

    //retrieve blog post 1, with all comments and authors
    //(so we can display the entire record on the UI while we are disconnected)
    blogpost = te.BlogPosts
        .Include(i => i.Comments.Select(j => j.Author)) 
        .SingleOrDefault(i => i.ID == 1);
}

//change the content
blogpost.Content = "New content " + DateTime.Now.ToString("HH:mm:ss");

//also want to change the author from Fred (2) to John (1)

//attempt 1 - try changing ID? - doesn't work (change is ignored)
//blogpost.AuthorID = 1;

//attempt 2 - try loading the author from the database? - doesn't work (Multiplicity constraint violated error on Author)
//using (TestEntities te = new TestEntities())
//{
//    te.Configuration.ProxyCreationEnabled = false;
//    te.Configuration.LazyLoadingEnabled = false;
//    blogpost.AuthorID = 1;
//    blogpost.Author = te.Authors.SingleOrDefault(i => i.ID == 1);
//}

//attempt 3 - try selecting the author already linked to the blogpost comment? - doesn't work (key values conflict during state painting)
//blogpost.Author = blogpost.Comments.First(i => i.AuthorID == 1).Author;
//blogpost.AuthorID = 1;


//attempt to save
using (TestEntities te = new TestEntities())
{
    te.Configuration.ProxyCreationEnabled = false;
    te.Configuration.LazyLoadingEnabled = false;
    te.Set<BlogPost>().Add(blogpost); // <-- (2) multiplicity error thrown here

    //paint the state ("unchanged" for everything except the blogpost which should be "modified")
    foreach (var entry in te.ChangeTracker.Entries())
    {
        if (entry.Entity is BlogPost)
            entry.State = EntityState.Modified;
        else
            entry.State = EntityState.Unchanged;  // <-- (3) key conflict error thrown here
    }

    //finished state painting, save changes
    te.SaveChanges();

}

Если вы используете этот код в EF5, используя наш существующий подход добавления .AsNoTracking() к исходному запросу.

        blogpost = te.BlogPosts
        .AsNoTracking()
        .Include(i => i.Comments.Select(j => j.Author)) 
        .SingleOrDefault(i => i.ID == 1);

.. и переопределяя GetHashCode и Equals на объектах: (например, в объекте BlogPost).

    public override int GetHashCode()
    {
        return this.ID;
    }

    public override bool Equals(object obj)
    {
        BlogPost tmp = obj as BlogPost;
        if (tmp == null) return false;
        return this.GetHashCode() == tmp.GetHashCode();
    }

.. все три подхода в коде теперь работают нормально.

Пожалуйста, расскажите, как это сделать в EF6? Благодаря

Ответы

Ответ 1

Интересно и удивительно, что вы получили свое приложение, работающее таким образом в EF5. EF всегда требует только одного экземпляра любого объекта. Если граф объектов добавлен, и EF неправильно предполагает, что он уже отслеживает объект, когда он фактически отслеживает другой экземпляр, тогда внутреннее состояние, которое EF отслеживает, будет непоследовательным. Например, график просто использует ссылки и коллекции .NET, поэтому график будет по-прежнему иметь несколько экземпляров, но EF будет отслеживать только один экземпляр. Это означает, что изменения свойств объекта не могут быть обнаружены правильно, а исправление между экземплярами также может привести к неожиданному поведению. Было бы интересно узнать, каким образом ваш код каким-то образом решил эти проблемы или если так случилось, что ваше приложение не попало ни в одну из этих проблем, и, следовательно, неверное отслеживание состояния не имело значения для вашего приложения.

Изменение, внесенное нами для EF6, делает менее вероятным, что приложение может получить отслеживание состояния EF в недопустимое состояние, которое затем приведет к неожиданному поведению. Если у вас есть умный шаблон, чтобы убедиться, что состояние отслеживания действительно, мы сломались с EF6, тогда было бы здорово, если бы вы могли записать ошибку с полным воспроизведением в http://entityframework.codeplex.com/.