这似乎是一个常见问题,但我找不到任何对我有用的答案。
我想使用EF6在应用中创建审核系统。检测简单属性中的更改不是问题,并且在保存新值之前创建具有原始值的审计条目很好。
但是,我还需要跟踪对引用属性的更改,即国家(链接到不同实体)已更改。
dbChangeTracker不会选择这些更改,即使它确实没有获得原始值。
有些人指出了ObjectContext的解决方案,但我看不出它会有什么帮助。有什么帮助吗?
其他人似乎表示我应该在实体上明确地放置FK属性,即Country_Id。
这是解决方案吗?
所有人都非常感谢。
干杯迈克
答案 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;
}
}
}