Entity Framework 6 - используйте мой getHashCode()
Для этого есть определенный фон - пожалуйста, потерпите меня!
У нас есть n-уровневое приложение WPF, использующее EF - мы загружаем данные из базы данных через dbContext в классы POCO. DbContext уничтожается, и пользователь затем может редактировать данные. Мы используем "рисование состояний", как предложено Джули Лерман в ее книге "Программирование 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 использовать OUR методы GetHashCode и Equals, как это было в EF5, или у кого-нибудь есть лучший способ справиться с добавлением корневого объекта в dbContext, который имеет дублированные дочерние объекты? в этом чтобы EF6 был бы доволен этим?
Спасибо за любую помощь. Простите за длинный пост.
ОБНОВЛЕНО Пошарив в коде EF, он выглядит как хэш-код InternalEntityEntry (dbEntityEntry), который раньше устанавливался путем получения хэш-кода объекта, но теперь в EF6 извлекается с помощью RuntimeHelpers.GetHashCode(_entity), что означает наш переопределенный хеш-код на объекте игнорируется. Поэтому я думаю, что заставить EF6 использовать наш хэш-код не может быть и речи, поэтому, возможно, мне нужно сконцентрироваться на том, как добавить сущность в контекст, который потенциально дублирует дочерние сущности, не нарушая EF. Какие-либо предложения?
ОБНОВЛЕНИЕ 2 Самое досадное в том, что об этом изменении в функциональности сообщается как о хорошем, а не, как мне кажется, о кардинальном изменении! Конечно, если у вас есть отключенные сущности, и вы загрузили их с помощью.AsNoTracking() для повышения производительности (а также потому, что мы знаем, что собираемся отключить их, поэтому зачем их отслеживать), то у dbContext нет причин переопределять наш метод getHashcode!
ОБНОВЛЕНИЕ 3 Спасибо за все комментарии и предложения - очень ценю! После некоторых экспериментов это, по-видимому, связано с.AsNoTracking(). Если вы загружаете данные с помощью.AsNoTracking(), то дублированные дочерние объекты являются отдельными объектами в памяти (с разными хеш-кодами), поэтому возникает проблема рисования состояния и последующего их сохранения. Мы исправили эту проблему ранее, переопределив хеш-коды, поэтому, когда сущности добавляются обратно в контекст сохранения, дублирующиеся сущности распознаются как один и тот же объект и добавляются только один раз, но мы больше не можем делать это с EF6. Так что теперь мне нужно выяснить, почему мы использовали.AsNoTracking() в первую очередь. У меня есть еще одна мысль: возможно, трекер изменений EF6 должен использовать свой собственный метод генерации хеш-кода только для записей, которые он активно отслеживает - если сущности были загружены с помощью.AsNoTracking(), может, вместо этого ему следует использовать хеш-код из базовой сущности?
ОБНОВЛЕНИЕ 4 Итак, теперь мы убедились, что не можем продолжать использовать наш подход (переопределенные хэш-коды и.AsNoTracking) в EF6, как мы должны управлять обновлениями для отключенных объектов? Я создал этот простой пример с блогами / комментариями / авторами:
В этом примере я хочу открыть блог 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/.