該解決方案是基於@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();
}