Я переопределяю SaveChanges в моем DbContext для реализации журнала аудита. Работать с отношениями «многие ко многим» или независимыми ассоциациями относительно легко, поскольку EF создает ObjectStateEntries для любых изменений в таких типах отношений.
Я использую ассоциации внешних ключей, и когда отношение между сущностями изменяется, все, что вы получаете, это ObjectStateEnty, который говорит, например, что у сущности "Title" изменено свойство "PublisherID". Для человека это, очевидно, внешний ключ в объекте Title, но как мне определить это во время выполнения? Есть ли способ перевести это изменение в свойство «PublisherID», чтобы позволить EntityKey для объекта, который представляет внешний ключ?
Я предполагаю, что имею дело с сущностями, которые выглядят так:
public sealed class Publisher
{
public Guid ID { get; set; }
public string Name { get; set; }
public ICollection<Title> Titles { get; set; }
}
public class Title
{
public Guid ID { get; set; }
public string Name { get; set; }
public Guid? PublisherID { get; set; }
public Publisher Publisher { get; set; }
}
Существует также код EF EntityConfiguration, который определяет отношение и внешний ключ:
public TitleConfiguration()
{
HasOptional<Publisher>(t => t.Publisher).WithMany(
p => p.Titles).HasForeignKey(t => t.PublisherID);
}
То, что я сейчас делаю, кажется слишком сложным. Я надеюсь, что есть более элегантный способ достичь своей цели. Для каждого измененного свойства из ObjectStateEntry я просматриваю все ReferentialConstraints для текущей сущности и проверяю, используют ли какие-либо из них его в качестве внешнего ключа. Код ниже вызывается из SaveChanges ():
private void HandleProperties(ObjectStateEntry entry,
ObjectContext ctx)
{
string[] changedProperties = entry.GetModifiedProperties().ToArray();
foreach (string propertyName in changedProperties)
{
HandleForeignKey(entry, ctx, propertyName);
}
}
private void HandleForeignKey(ObjectStateEntry entry,
ObjectContext ctx, string propertyName)
{
IEnumerable<IRelatedEnd> relatedEnds =
entry.RelationshipManager.GetAllRelatedEnds();
foreach (IRelatedEnd end in relatedEnds)
{
// find foreign key relationships
AssociationType elementType = end.RelationshipSet.ElementType as
AssociationType;
if (elementType == null || !elementType.IsForeignKey) continue;
foreach (ReferentialConstraint constraint in
elementType.ReferentialConstraints)
{
// Multiplicity many means we are looking at a foreign key in a
// dependent entity
// I assume that ToRole will point to a dependent entity, don't
// know if it can be FromRole
Debug.Assert(constraint.ToRole.RelationshipMultiplicity ==
RelationshipMultiplicity.Many);
// If not 1 then it is a composite key I guess.
// Becomes a lot more difficult to handle.
Debug.Assert(constraint.ToProperties.Count == 1);
EdmProperty prop = constraint.ToProperties[0];
// entity types of current entity and foreign key entity
// must be the same
if (prop.DeclaringType == entry.EntitySet.ElementType
&& propertyName == prop.Name)
{
EntityReference principalEntity = end as EntityReference;
if (principalEntity == null) continue;
EntityKey newEntity = principalEntity.EntityKey;
// if there is more than one, the foreign key is composite
Debug.Assert(newEntity.EntityKeyValues.Length == 1);
// create an EntityKey for the old foreign key value
EntityKey oldEntity = null;
if (entry.OriginalValues[prop.Name] is DBNull)
{
oldEntity = new EntityKey();
oldEntity.EntityKeyValues = new[] {
new EntityKeyMember("ID", "NULL")
};
oldEntity.EntitySetName = newEntity.EntitySetName;
}
else
{
Guid oldGuid = Guid.Parse(
entry.OriginalValues[prop.Name].ToString());
oldEntity = ctx.CreateEntityKey(newEntity.EntitySetName,
new Publisher()
{
ID = oldGuid
});
}
Debug.WriteLine(
"Foreign key {0} changed from [{1}: {2}] to [{3}: {4}]",
prop.Name,
oldEntity.EntitySetName, oldEntity.EntityKeyValues[0],
newEntity.EntitySetName, newEntity.EntityKeyValues[0]);
}
}
}
}
Надеюсь, это поможет лучше проиллюстрировать то, чего я пытаюсь достичь. Любые входные данные приветствуются.
Спасибо!