是否有可能检测到EF6中实体的引用属性何时发生了变化?

时间:2014-04-08 19:27:53

标签: c# .net entity-framework

这似乎是一个常见问题,但我找不到任何对我有用的答案。

我想使用EF6在应用中创建审核系统。检测简单属性中的更改不是问题,并且在保存新值之前创建具有原始值的审计条目很好。

但是,我还需要跟踪对引用属性的更改,即国家(链接到不同实体)已更改。

dbChangeTracker不会选择这些更改,即使它确实没有获得原始值。

有些人指出了ObjectContext的解决方案,但我看不出它会有什么帮助。有什么帮助吗?

其他人似乎表示我应该在实体上明确地放置FK属性,即Country_Id。

这是解决方案吗?

所有人都非常感谢。

干杯迈克

2 个答案:

答案 0 :(得分:2)

实现一种方法,用于检索有关EntityObject的某些属性的元数据信息:

private static EdmMember GetEdmMember<TEntity>(this ObjectContext context, TEntity entityContainer, string propertyName)
 {
    EdmMember edmMember = null;
    EntityType entityType = context.MetadataWorkspace.GetItem<EntityType>(entityContainer.GetType().FullName, DataSpace.CSpace);
    IEnumerable<EdmMember> edmMembers = entityType.MetadataProperties.First(p => p.Name == "Members").Value as IEnumerable<EdmMember>;
    edmMember = edmMembers.FirstOrDefault(item => item.Name == propertyName);
    if (edmMember == null)
        throw new ArgumentException(
                            string.Format("Cannot find property metadata: property '{0}' in '{1}' entity object", propertyName, entityType.Name));
    return edmMember;
}

MetadataWorkspace类是一个中央运行时API,可用于在应用程序上下文中与实体数据模型(EDM)元数据进行交互。有关更多信息,请参阅 http://msdn.microsoft.com/en-us/library/bb387116%28v=vs.110%29.aspx

接下来,实现IsPropertyChanged方法,如下所示:

public static bool IsPropertyChanged<TEntity>(this ObjectContext context, TEntity entityContainer, string propertyName)
            where TEntity : IEntityWithKey, IEntityWithRelationships
{
    bool isModified = false;
    EdmMember edmMember = GetEdmMember(context, entityContainer, propertyName);

    switch (edmMember.BuiltInTypeKind)
    {
        case BuiltInTypeKind.NavigationProperty: /*navigation property*/
                        {
                            NavigationProperty navigationProperty = edmMember as NavigationProperty;
                            IRelatedEnd sourceRelatedEnd = entityContainer.RelationshipManager.GetRelatedEnd(navigationProperty.RelationshipType.FullName,
                                                                                                             navigationProperty.ToEndMember.Name) as IRelatedEnd;
                            EntityState state = (EntityState.Added | EntityState.Deleted);
                            IEnumerable<IGrouping<IRelatedEnd, ObjectStateEntry>> relationshipGroups = GetRelationshipsByRelatedEnd(context, entityContainer, state);
                            foreach (var relationshipGroup in relationshipGroups)
                            {
                                IRelatedEnd targetRelatedEnd = (IRelatedEnd)relationshipGroup.Key;
                                if (targetRelatedEnd.IsEntityReference()
                                    && targetRelatedEnd.IsRelatedEndEqual(sourceRelatedEnd))
                                {
                                    isModified = true;
                                    break;
                                }
                            }
                        } break;

        case BuiltInTypeKind.EdmProperty: /*scalar field*/
                        {
                            ObjectStateEntry containerStateEntry = null;
                            isModified = context.IsScalarPropertyModified(propertyName, entityContainer, out containerStateEntry);
                        } break;

        default:
                        {
                            throw new InvalidOperationException("Property type not supported");
                        }
    }

    return isModified;
}

和标量属性更改跟踪的方法:

private static bool IsScalarPropertyModified(this ObjectContext context, string scalarPropertyName, IEntityWithKey entityContainer, out ObjectStateEntry containerStateEntry)
                {
                    bool isModified = false;
                    containerStateEntry = context.ObjectStateManager.GetObjectStateEntry(entityContainer.EntityKey);
                    IEnumerable<string> modifiedProperties = containerStateEntry.GetModifiedProperties();

                    string changedProperty = modifiedProperties.FirstOrDefault(element => (element == scalarPropertyName));
                    isModified = (null != changedProperty);

                    if (isModified) 
                    {
                        object originalValue = containerStateEntry.OriginalValues[changedProperty];
                        object currentValue = containerStateEntry.CurrentValues[changedProperty];
                        //sometimes property can be treated as changed even though you set the same value it had before
                        isModified = !object.Equals(originalValue, currentValue);
                    }

                    return isModified;
                }

跟踪标量属性很简单,对。如果您的实体中包含外键,您可以随时检查这些外键是否已更改(确保对象中的FK字段与导航属性保持同步,这通常是一个问题)。 上面代码中的导航属性案例应该让您了解实现,在大多数情况下应该可以使用。

PS。如果您正在实施审计系统,那么您可能希望了解一下EF 6.1中引入的拦截器(只是一个想法) http://msdn.microsoft.com/en-US/data/jj556606#Interceptors

答案 1 :(得分:0)

我建议在保存更改时跟踪FK属性的更改就足够了。毕竟,如果我这样做,我将有兴趣改变一个实体与另一个实体的关系。这是一个非常简单的实现,假设所有FK属性都以“Id”结尾并且是整数。

foreach (var entry in context.ChangeTracker.Entries())
{
    foreach (var prop in entry.Entity.GetType().GetProperties())
    {
        if (prop.Name.EndsWith("Id") && 
            !prop.PropertyType.IsAssignableFrom(typeof(int)) && 
            context.Entry(entry.Entity).Property(prop.Name).IsModified)
        {
            // Log things like old value and new:
            var propEntry = context.Entry(entry.Entity).Property(prop.Name);
            var currentValue = propEntry.CurrentValue;
            var oldValue = propEntry.OriginalValue;
        }

    }
}