该解决方案是基于@JamesR's answer。
我的目标是让代码更通用,因此它可以用于连接到不同表的多个外键。
改进值得注意的是:
我感动的是得到propertyName的foreach
循环之外的外键列表中的代码。由于FK列表不会根据特定属性而改变,因此没有理由每次都检索新列表。如果系统中有许多FK,这可能需要一段时间,所以您不希望不必要地重复该过程。
而不是硬编码的一类特殊类型的像GetType(typeof(User)
,我使用检索从FK外键表名:
string lookUpTableName = thisFk.ReferentialConstraints[0].FromRole.Name;
然后,虽然引用的FK属性名称通常是ID
,因为它可以改变,我检索到的FK属性的名称,以及:
string lookUpPropertyName = thisFk.ReferentialConstraints[0].FromProperties[0].Name;
然后我用ObjectContext.ExecuteStoreQuery
动态插上表和列名和检索外键文本值。
如果某个属性是FK,我将为原始的新值获取FK文本值。
完整代码:
首先,获取系统中所有外键的列表。
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();
然后,循环访问属性列表,并在存在FK时使用外键值替换原始值和当前值。
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;
}
}
这是我用来建立SQL的CommandText的方法:
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();
}