DbEntityEntry.OriginalValues不填充复杂属性

See*_*vil 5 entity-framework entity-framework-4.1

我正在编写一个来自网上代码片段的审计线索.在调用我的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);
            }
        }
Run Code Online (Sandbox Code Playgroud)

然后,当我尝试访问修改后的实体的原始值时,将填充所有标量属性,但复杂的属性不存在(属性计数将为6而不是8).然后我调用ToObject()以原始状态构建对象,但显然复杂属性都是空值.

modifiedEntity.OriginalValues.ToObject()
Run Code Online (Sandbox Code Playgroud)

这只发生在我的一些域对象中,并且这些对象在ToObject()调用之后总是显示为代理,而(我不确定为什么)但没有实体为它们创建代理的那些,它们的复杂属性填充得很好.当我在我的应用程序中正常使用POCO代理时,延迟加载可以正常工作.

我注意到如果我对其中一个未作为OriginalValues数据的一部分填充的复杂属性进行更改,则对象的状态不会更改为Modified,这是有意义的,因为更改跟踪将原始值与当前值进行比较看它是否改变了.什么没有什么意义的是,该数据仍然坚持在SaveChanged?

编辑:我刚刚注意到,模型对象确实填充了它的复杂属性,所讨论的复杂属性(按惯例)被实体视为"复杂类型",即没有主键.

有任何想法吗?

dan*_*dan 8

要获取实体的所有成员名称而不仅仅是您可以使用的简单属性,ObjectContext而不是DbContext通过以下方式访问成员列表EntityType.

((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(dbEntry).EntitySet.ElementType.Members
Run Code Online (Sandbox Code Playgroud)

然后,您可以使用方法DbEntityEntry.Member(string propertyName)来获取DbMemberEntry.

获取表示实体成员的对象.返回对象的运行时类型将根据要求的成员类型而有所不同.当前支持的成员类型及其返回类型是引用导航属性(DbReferenceEntry),Collection导航属性(DbCollectionEntry),Primitive /标量属性(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>();
}
Run Code Online (Sandbox Code Playgroud)

我期待看到其他解决方案.


agr*_*adl 4

我相信这篇文章可以给你一些启发。它不是 EF 4.1,但许多提示和示例都适用。

复杂类型和新的更改跟踪 API

教程刚过半,该部分的标题就是链接的名称。基本上,要访问复杂类型的原始值,您需要添加一个额外的函数来指定复杂属性。

var original = modifiedEntity.ComplexProperty(u => u.Address).OriginalValues
Run Code Online (Sandbox Code Playgroud)