DbEntityEntry.OriginalValues не заполняет сложные свойства
Я пишу контрольный журнал из фрагментов кода, найденного в Интернете. При вызове моей функции SaveChanges я перебираю все измененные сущности, зарегистрированные в Context, и строю записи в журнале по их изменениям.
foreach (DbEntityEntry modifiedEntity in this.ChangeTracker.Entries().Where(p => p.State == System.Data.EntityState.Added || p.State == System.Data.EntityState.Deleted || p.State == System.Data.EntityState.Modified))
{
// For each changed record, get the audit record entries and add them
foreach(AuditLog x in GetAuditRecordsForChange(modifiedEntity, userId))
{
this.AuditLog.Add(x);
}
}
Когда я затем пытаюсь получить доступ к исходным значениям модифицированного объекта, все скалярные свойства заполняются, но сложные свойства не существуют (количество свойств будет, скажем, 6 вместо 8). Я тогда звоню ToObject()
чтобы построить объект в его первоначальном состоянии, но очевидно, что сложные свойства все нулевые.
modifiedEntity.OriginalValues.ToObject()
Это происходит только с некоторыми из моих доменных объектов, и эти объекты всегда отображаются как прокси после ToObject()
называть тогда (я не уверен почему), но те, у которых нет прокси, созданных для них сущностью, их сложные свойства заполняют нормально. Когда я использую прокси-серверы POCO как обычно во всем приложении, отложенная загрузка работает на них нормально.
Я заметил, что если я внесу изменение в одно из этих сложных свойств, которые не заполняются как часть данных OriginalValues, состояние объекта не изменится на Изменено, это имеет смысл, поскольку отслеживание изменений сравнивает исходные значения с текущими чтобы увидеть, если это изменилось. Что не имеет смысла, так это то, что данные все еще сохраняются в SaveChanged??
РЕДАКТИРОВАТЬ: Я только что заметил, объект модели, который заполняет свои сложные свойства, рассматриваемое комплексное свойство (по соглашению) считается "сложным типом" Entity, то есть не первичный ключ.
Есть идеи?
3 ответа
Чтобы получить все имена членов сущности, а не только простые свойства, с которыми вы можете работать ObjectContext
скорее, чем DbContext
затем получить доступ к списку членов через EntityType
,
((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(dbEntry).EntitySet.ElementType.Members
Затем вы можете использовать метод DbEntityEntry.Member(string propertyName), чтобы получить DbMemberEntry.
Получает объект, который представляет член сущности. Тип времени выполнения возвращаемого объекта будет зависеть от типа запрашиваемого члена. Поддерживаемые типы элементов и их возвращаемые типы: свойство навигации по ссылкам (DbReferenceEntry), свойство навигации по коллекциям (DbCollectionEntry), свойство примитива / скаляра (DbPropertyEntry) и свойство Complex (DbComplexPropertyEntry).
Пример кода ниже использует это для регистрации изменений сложных свойств. Обратите внимание, что, вероятно, что-то более сексуальное должно быть сделано при регистрации изменений сложных свойств - в настоящее время я регистрирую все комплексное свойство (сериализованное в JSON), а не только внутренние свойства, которые изменились, но он выполняет свою работу.
private IEnumerable<AuditLogEntry> GetAuditLogEntries(DbEntityEntry dbEntry)
{
if (dbEntry.State == EntityState.Added)
{
return new AuditLogEntry { ... };
}
if (dbEntry.State == EntityState.Deleted)
{
return new AuditLogEntry { ... };
}
if (dbEntry.State == EntityState.Modified)
{
// Create one AuditLogEntry per updated field.
var list = new List<AuditLogEntry>();
// We need to object state entry to do deeper things.
ObjectStateEntry objectStateEntry = ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(dbEntry);
// Iterate over the members (i.e. properties (including complex properties), references, collections) of the entity type
foreach (EdmMember member in ((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(dbEntry).EntitySet.ElementType.Members)
{
var dbMemberEntry = dbEntry.Member(member.Name) as DbPropertyEntry;
if (dbMemberEntry == null || Equals(dbMemberEntry.OriginalValue, dbMemberEntry.CurrentValue))
{
// Member entry isn't a property entry or it isn't modified.
continue;
}
string oldValue;
string newValue;
if (dbMemberEntry is DbComplexPropertyEntry)
{
// Bit a bit lazy here and just serialise the complex property to JSON rather than detect which inner properties have changed.
var complexProperty = (DbComplexPropertyEntry)dbMemberEntry;
oldValue = EntitySerialiser.Serialise(complexProperty.OriginalValue as IAuditableComplexType);
newValue = EntitySerialiser.Serialise(complexProperty.CurrentValue as IAuditableComplexType);
}
else
{
// It's just a plain property, get the old and new values.
var property = dbMemberEntry;
oldValue = property.OriginalValue.ToStringOrNull();
newValue = property.CurrentValue.ToStringOrNull();
}
list.Add(new AuditLogEntry
{
...,
EventType = AuditEventType.Update,
ColumnName = member.Name,
OriginalValue = oldValue,
NewValue = newValue
});
}
return list;
}
// Otherwise empty.
return Enumerable.Empty<AuditLogEntry>();
}
Я с нетерпением жду встречи с другими решениями для этого.
Я считаю, что эта статья может дать вам некоторое представление. Это не EF 4.1, но применимы многие советы и примеры.
Сложные типы и новый API отслеживания изменений
Это немного раньше, чем на полпути учебника с названием раздела, который является названием ссылки. В основном для доступа к исходным значениям со сложным типом вы добавляете дополнительную функцию, указывающую сложное свойство.
var original = modifiedEntity.ComplexProperty(u => u.Address).OriginalValues
Более того, кажется, что отслеживание изменений EF не сохраняет какие-либо исходные значения для свойств ссылки или типа коллекции на измененных объектах (кто-то, пожалуйста, исправьте меня, если я ошибаюсь)
Например, я могу выяснить, что у моей сущности Vehicle была удалена ссылка на один объект VehicleColour, а затем повторно добавлена ссылка на другой экземпляр VehicleColour. Я не могу найти, например, что он указывал на VehicleColour с именем "Stardust Silver" и теперь указывает на один с "Azure Blue".