Это решение основано на @JamesR's answer.
Моя цель состояла в том, чтобы сделать код более общим, чтобы он мог использоваться для нескольких внешних ключей, подключающихся к различным таблицам.
Улучшения стоит отметить:
Я переместил код, который получает список внешних ключей вне цикла ИмениСвойства foreach
. Поскольку список FK не изменяется в зависимости от конкретного свойства, нет никаких оснований каждый раз получать новый список. Если в системе много FK, это может занять некоторое время, поэтому вы не захотите повторять процесс без необходимости.
Вместо жесткого кодирования определенного типа класса, как GetType(typeof(User)
, я извлек внешних ключей таблицы из ФК с помощью:
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();
}