DbEntityEntry.OriginalValues不填充复杂属性

时间:2011-08-11 08:10:01

标签: 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);
            }
        }

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

modifiedEntity.OriginalValues.ToObject()

这只发生在我的域对象的部分中,并且这些对象在ToObject()调用后始终显示为代理,而(我不知道为什么)但是那些' t具有由实体为它们创建的代理,它们的复杂属性可以很好地填充。当我在我的应用程序中正常使用POCO代理时,延迟加载对它们起作用很好。

我注意到如果我对其中一个未作为OriginalValues数据的一部分填充的复杂属性进行更改,则对象的状态不会更改为Modified,这是有意义的,因为更改跟踪比较原始当前值以查看它是否已更改。 没有有意义的是,数据仍然保存在SaveChanged上?

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

有什么想法吗?

3 个答案:

答案 0 :(得分:8)

要获取实体的所有成员名称,而不仅仅是您可以使用ObjectContext而非DbContext的简单属性,然后通过EntityType访问成员列表。< / p>

((IObjectContextAdapter)this).ObjectContext.ObjectStateManager.GetObjectStateEntry(dbEntry).EntitySet.ElementType.Members

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

  

获取表示实体成员的对象。返回对象的运行时类型将根据要求的成员类型而有所不同。当前支持的成员类型及其返回类型是引用导航属性(DbReferenceEntry),Collection导航属性(DbCollectionEntry),Primitive / scalar属性(DbPropertyEntry)和Complex属性(DbComplexPropertyEntry)。

下面的代码示例使用它来记录复杂属性的修改。请注意,在记录复杂的属性更改时,可能还有一些比较性感的东西 - 我正在记录整个复杂属性(序列化为JSON),而不仅仅是已更改的内部属性,但它完成了工作。 / p>

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>();
}

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

答案 1 :(得分:4)

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

Complex Types and the New Change Tracking API

它在教程中途之前有一点,该部分的标题是链接的名称。基本上,要访问具有复杂类型的原始值,您需要添加一个额外的函数来指定复杂属性。

var original = modifiedEntity.ComplexProperty(u => u.Address).OriginalValues

答案 2 :(得分:1)

更多挖掘,似乎EF更改跟踪不会在修改后的实体上存储任何类型的参考或集合类型属性的原始值(如果我错了,请有人纠正我)

我可以找到例如我的Vehicle实体已经删除了一个VehicleColour对象的引用,然后重新添加指向VehicleColour的不同实例。我无法找到例如它确实指向一个名为“Stardust Silver”的VehicleColour,现在指向一个“Azure Blue”。