Для этого нужно определенное количество фона, пожалуйста, несите меня!
У нас есть приложение 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:
В этом примере я хочу открыть 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? Благодаря