C# 如何从DBEntityEntry获取外键值?
我正在尝试使用实体框架创建保存点的任何更改的审核日志。到目前为止,我已经让它工作得相当好,使用下面的代码存储对每个字段所做的所有更改:C# 如何从DBEntityEntry获取外键值?,c#,.net,entity-framework,C#,.net,Entity Framework,我正在尝试使用实体框架创建保存点的任何更改的审核日志。到目前为止,我已经让它工作得相当好,使用下面的代码存储对每个字段所做的所有更改: foreach (string propertyName in dbEntry.OriginalValues.PropertyNames) { // For updates, we only want to capture the columns that actually changed if (!object.Equals(dbEntry.O
foreach (string propertyName in dbEntry.OriginalValues.PropertyNames)
{
// For updates, we only want to capture the columns that actually changed
if (!object.Equals(dbEntry.OriginalValues.GetValue<object>(propertyName), dbEntry.CurrentValues.GetValue<object>(propertyName)))
{
result.Add(new AuditLog()
{
UserID = UserId,
EventDateUTC = changeTime,
EventType = "M", // Modified
TableName = tableName,
RecordID = primaryKey.ToString(),
ColumnName = propertyName,
OriginalValue = dbEntry.OriginalValues.GetValue<object>(propertyName) == null ? null : dbEntry.OriginalValues.GetValue<object>(propertyName).ToString(),
NewValue = dbEntry.CurrentValues.GetValue<object>(propertyName) == null ? null : dbEntry.CurrentValues.GetValue<object>(propertyName).ToString()
});
}
}
foreach(dbEntry.OriginalValues.PropertyNames中的字符串propertyName)
{
//对于更新,我们只希望捕获实际更改的列
如果(!object.Equals(dbEntry.OriginalValues.GetValue(propertyName),dbEntry.CurrentValues.GetValue(propertyName)))
{
结果.添加(新的审核日志()
{
UserID=UserID,
EventDateUTC=changeTime,
EventType=“M”,//已修改
TableName=TableName,
RecordID=primaryKey.ToString(),
ColumnName=propertyName,
OriginalValue=dbEntry.OriginalValue.GetValue(propertyName)==null?null:dbEntry.OriginalValue.GetValue(propertyName).ToString(),
NewValue=dbEntry.CurrentValues.GetValue(propertyName)==null?null:dbEntry.CurrentValues.GetValue(propertyName).ToString()
});
}
}
我面临的问题是如何获取属于此对象的任何外键的值。例如:我有一个vehicle对象,它与一系列查找表(如变速箱、型号等)有关系。如果这些值更改,审计表将存储更改的id,但我希望存储实际值
在这种情况下,有没有办法获取外键值?好的。。。这是一个老问题,但我花了最后一段时间来解决这个问题,因为我有完全相同的要求。也许有更简单的方法,但我使用的代码如下: 您的原始代码,为我的目的稍作修改(RecordID始终是int),并调用新方法来计算新值
foreach (string propertyName in dbEntry.OriginalValues.PropertyNames)
{
// For updates, we only want to capture the columns that actually changed
if (!Equals(dbEntry.OriginalValues.GetValue<object>(propertyName), dbEntry.CurrentValues.GetValue<object>(propertyName)))
{
var newVal = getNewValueAsString(dbEntry, tableName, propertyName);
result.Add(new AuditLog
{
UserID = currentUser.ID,
Timestamp = changeTime,
EventType = EventType.Modified,
TableName = tableName,
RecordID = dbEntry.OriginalValues.GetValue<int>(keyName),
ColumnName = propertyName,
OriginalValue = dbEntry.OriginalValues.GetValue<object>(propertyName) == null ? null : dbEntry.OriginalValues.GetValue<object>(propertyName).ToString(),
NewValue = newVal
}
);
}
}
用IsName属性标记外键模型的“name”属性(注意,如果找不到,代码将默认为名为“name”的属性)
以及《重型起重守则》
private string getNewValueAsString(DbEntityEntry dbEntry, string tableName, string propertyName)
{
var fkVal = getForeignKeyValue(tableName, propertyName, dbEntry.CurrentValues.GetValue<object>(propertyName));
return fkVal != null ? fkVal.ToString()
: (dbEntry.CurrentValues.GetValue<object>(propertyName) == null ? null
: dbEntry.CurrentValues.GetValue<object>(propertyName).ToString());
}
private object getForeignKeyValue(string tableName, string propertyName, object foreignKeyID)
{
// if this property is part of a foreign key, we need to instead look that up and store the value of the
// foreign key
// first get all the foreign keys in the system
var workspace = ((IObjectContextAdapter)this).ObjectContext.MetadataWorkspace;
var items = workspace.GetItems<AssociationType>(DataSpace.CSpace);
if (items == null) return null;
var fk = items.Where(a => a.IsForeignKey).ToList();
// now we look into the FK attributes and find that the "To Role" is out current table, and the
// "To Property" is out current property. The underscore is a bit of an assumption that the foreign
// key name built by EF will be ENTITY_BLAH_BLAH
var thisFk = fk.Where(x => x.ReferentialConstraints[0].ToRole.Name.StartsWith(tableName + "_"))
.FirstOrDefault(x => x.ReferentialConstraints[0].ToProperties[0].Name == propertyName);
// if fkname has no results, this is not a foreign key and we are done
if (thisFk == null) return null;
// Now that we know the foriegn key, we need to lookup the Name value in the other table
// find the assembly
var assembly = Assembly.GetCallingAssembly();
// build the type for the foreign key entity
// e.g. if the current entity is Task, and the property is StatusID, we are
// getting the "TaskStatus" type with reflection
// "User" class is an object in the Models namespace - you could just hardcode the string if you want
var foreignKeyType = assembly.GetType(typeof(User).Namespace + "." +
thisFk.ReferentialConstraints[0].FromRole.GetEntityType().Name);
// get the DbSet, same as: "(new DBContext()).EntityName"
var fkSet = Set(foreignKeyType);
// and find the row in that table
var fkItem = fkSet.Find(foreignKeyID);
// find the first column marked with the "IsName" attribute, otherwise default to "Name"
var nameColProperty = foreignKeyType.GetProperties()
.FirstOrDefault(p => p.GetCustomAttributes(typeof(IsNameAttribute), false).Any());
string nameCol = "Name";
if (nameColProperty != null) nameCol = nameColProperty.Name;
var nameColProperty2 = fkItem.GetType().GetProperty(nameCol);
if (nameColProperty2 == null) return null;
// get the value
var fkValue = nameColProperty2.GetValue(fkItem, null);
// and now, my brain hurts
return fkValue;
}
私有字符串getNewValueAsString(DbEntityEntry dbEntry,string tableName,string propertyName)
{
var fkVal=getForeignKeyValue(tableName,propertyName,dbEntry.CurrentValues.GetValue(propertyName));
返回fkVal!=null?fkVal.ToString()
:(dbEntry.CurrentValues.GetValue(propertyName)=null?null
:dbEntry.CurrentValues.GetValue(propertyName.ToString());
}
私有对象getForeignKeyValue(字符串表名、字符串属性名称、对象foreignKeyID)
{
//如果这个属性是外键的一部分,我们需要查找它并存储
//外键
//首先获取系统中的所有外键
var workspace=((IObjectContextAdapter)this.ObjectContext.MetadataWorkspace;
var items=workspace.GetItems(DataSpace.CSpace);
如果(items==null)返回null;
var fk=items.Where(a=>a.IsForeignKey.ToList();
//现在我们查看FK属性,发现“To Role”在当前表之外,而
//“To Property”是当前属性之外的属性。下划线有点假设
//EF生成的关键名称将是实体____________
var thisFk=fk.Where(x=>x.ReferentialConstraints[0].ToRole.Name.StartsWith(tableName+“”))
.FirstOrDefault(x=>x.ReferentialConstraints[0].TopProperties[0].Name==propertyName);
//如果fkname没有结果,那么这不是外键,我们就完成了
if(thisFk==null)返回null;
//现在我们知道了外键,我们需要在另一个表中查找Name值
//查找程序集
var assembly=assembly.GetCallingAssembly();
//生成外键实体的类型
//例如,如果当前实体为Task,属性为StatusID,则我们为
//使用反射获取“TaskStatus”类型
//“User”类是Models名称空间中的一个对象-如果需要,可以对字符串进行硬编码
var foreignKeyType=assembly.GetType(typeof(User).Namespace+”+
thisFk.ReferentialConstraints[0].FromRole.GetEntityType().Name);
//获取数据库集,如下所示:“(new DBContext()).EntityName”
var fkSet=Set(foreignKeyType);
//然后找到表中的行
var fkItem=fkSet.Find(foreignKeyID);
//查找标有“IsName”属性的第一列,否则默认为“Name”
var nameColProperty=foreignKeyType.GetProperties()
.FirstOrDefault(p=>p.GetCustomAttributes(typeof(IsNameAttribute),false).Any());
字符串nameCol=“Name”;
如果(nameColProperty!=null)nameCol=nameColProperty.Name;
var nameColProperty2=fkItem.GetType().GetProperty(nameCol);
if(nameColProperty2==null)返回null;
//获取值
var fkValue=nameColProperty2.GetValue(fkItem,null);
//现在,我的大脑受伤了
返回值;
}
此解决方案基于
我的目标是使代码更通用,以便它可以用于连接到不同表的多个外键
值得注意的改进:
- 我将获取外键列表的代码移到propertyName
循环之外。由于FKs列表不会根据特定属性更改,因此没有理由每次都检索新列表。如果系统中有许多FK,这可能需要一段时间,因此您不希望不必要地重复该过程foreach
- 我没有硬编码特定的类类型,如
,而是使用以下方法从FK检索外键表名:GetType(typeof(User)
然后,尽管引用的FK属性名称通常是string lookUpTableName = thisFk.ReferentialConstraints[0].FromRole.Name;
,但由于它可能会有所不同,因此我也检索了FK属性名称:ID
然后我用string lookUpPropertyName = thisFk.ReferentialConstraints[0].FromProperties[0].Name;
string lookUpTableName = thisFk.ReferentialConstraints[0].FromRole.Name;
string lookUpPropertyName = thisFk.ReferentialConstraints[0].FromProperties[0].Name;
IObjectContextAdapter contextAdapter = ((IObjectContextAdapter)this); MetadataWorkspace workspace = contextAdapter.ObjectContext.MetadataWorkspace; var items = workspace.GetItems<AssociationType>(DataSpace.CSpace); List<AssociationType> FKList = items == null ? null : items.Where(a => a.IsForeignKey).ToList();
foreach (string propertyName in entry.OriginalValues.PropertyNames) { var original = entry.OriginalValues.GetValue<object>(propertyName); var current = entry.CurrentValues.GetValue<object>(propertyName); if (FKList != null) { GetPossibleForeignKeyValues(tableName, propertyName, ref original, ref current, FKList, contextAdapter); } if ((original == null && current != null) || (original != null && !original.Equals(current))) { result.Add(new AuditLog() { UserID = UserId, EventDateUTC = changeTime, EventType = "M", // Modified TableName = tableName, RecordID = primaryKey.ToString(), ColumnName = propertyName, OriginalValue = original != null ? original.ToString() : "NULL", NewValue = current != null ? current.ToString() : "NULL" }); } }
private void GetPossibleForeignKeyValues(string tableName, string propertyName, ref object originalFKValue, ref object newFKValue, List<AssociationType> FKList, IObjectContextAdapter contextAdapter) { // If this property is part of a foreign key, look up and set the FKValue to the text // value of the foreign key. Otherwise, just leave the FKValue alone. // Look into the FK attributes and find that the "To Role" is out current table, // and the "To Property" is out current property. AssociationType thisFk = FKList.FirstOrDefault(x => tableName.Contains(x.ReferentialConstraints[0].ToRole.Name) && propertyName.Contains(x.ReferentialConstraints[0].ToProperties[0].Name)); // If fkname has no results, this is not a foreign key and we are done. if (thisFk != null) { // Now that we know the foriegn key, look up the Name value in the other table. string lookUpTableName = thisFk.ReferentialConstraints[0].FromRole.Name; string lookUpPropertyName = thisFk.ReferentialConstraints[0].FromProperties[0].Name; //Assuming the FK column name is "Name". //Use the idea in @JamesR's solution or some sort of LookUp table if it is not. string commandText = BuildCommandText("Name", lookUpTableName, lookUpPropertyName); originalFKValue = contextAdapter.ObjectContext .ExecuteStoreQuery<string>(commandText, new SqlParameter("FKID", originalFKValue)) .FirstOrDefault() ?? originalFKValue; newFKValue = contextAdapter.ObjectContext .ExecuteStoreQuery<string>(commandText, new SqlParameter("FKID", newFKValue)) .FirstOrDefault() ?? originalFKValue; } }
private string BuildCommandText(string columnName, string lookUpTableName, string lookUpPropertyName) { StringBuilder builder = new StringBuilder(); builder.Append("SELECT "); builder.Append(columnName); builder.Append(" FROM "); builder.Append(lookUpTableName); builder.Append(" WHERE "); builder.Append(lookUpPropertyName); builder.Append(" = @FKID"); //The result query will look something like: //SELECT ColumnName FROM TableName WHERE PropertyName = @FKID return builder.ToString(); }