实体框架:跟踪FK关联的更改 [英] Entity Framework: Tracking changes to FK associations

查看:113
本文介绍了实体框架:跟踪FK关联的更改的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在我的DbContext上覆盖SaveChanges以实现审核日志。使用多对多关系或独立关联相对容易,因为EF为这些关系的任何更改创建了ObjectStateEntries。



我正在使用外键关联,当实体之间的关系更改所有你得到的是一个ObjectStateEnty,例如实体标题已更改PublisherID属性。对于一个人来说,这显然是Title实体的外键,但是如何在运行时呢?有没有办法将此更改转换为PublisherID属性,让外部键代表实体的EntityKey?



我假设我正在处理实体看起来像这样:

  public sealed class Publisher 
{
public Guid ID {get;组; }
public string Name {get;组; }
public ICollection< Title>标题{get;组; }
}

public class标题
{
public Guid ID {get;组; }
public string Name {get;组; }
public Guid? PublisherID {get;组;
public Publisher Publisher {get;组; }
}

还有定义关系和外键的EF EntityConfiguration代码: / p>

  public TitleConfiguration()
{
HasOptional< Publisher>(t => t.Publisher)。 WithMany(
p => p.Titles).HasForeignKey(t => t.PublisherID);
}

我现在在做什么似乎有点太复杂了。我希望有更优雅的方式来实现我的目标。对于来自ObjectStateEntry的每个修改的属性,我将查看当前实体的所有参考证明,并查看其中任何一个使用它作为外键。下面的代码从SaveChanges()调用:$ / $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ b {
string [] changedProperties = entry.GetModifiedProperties()。ToArray();
foreach(更改的属性中的字符串属性)
{
HandleForeignKey(entry,ctx,propertyName);
}
}

private void HandleForeignKey(ObjectStateEntry entry,
ObjectContext ctx,string propertyName)
{
IEnumerable< IRelatedEnd> relatedEnds =
entry.RelationshipManager.GetAllRelatedEnds();

foreach(IRelatedEnd end in relatedEnds)
{
//找到外键关系
AssociationType elementType = end.RelationshipSet.ElementType as
AssociationType;
if(elementType == null ||!elementType.IsForeignKey)continue;

foreach(
中的参考限制约束b元素元素参数示例)
{
//多元意味着我们正在看一个
中的外键//依赖实体
//我假设ToRole将指向一个依赖实体,不要
//知道它是否可以是FromRole
Debug.Assert(constraint.ToRole.RelationshipMultiplicity ==
RelationshipMultiplicity.Many);
//如果不是1,那么它是一个复合键我猜。
//变得更难处理了。
Debug.Assert(constraint.ToProperties.Count == 1);
EdmProperty prop = constraint.ToProperties [0];

//当前实体和外键实体的实体类型
//必须相同
if(prop.DeclaringType == entry.EntitySet.ElementType
& ;& propertyName == prop.Name)
{
EntityReference principalEntity = end as EntityReference;
if(principalEntity == null)continue;

EntityKey newEntity = principalEntity.EntityKey;
//如果有多个外键是复合的
Debug.Assert(newEntity.EntityKeyValues.Length == 1);

//为旧外键值创建一个EntityKey
EntityKey oldEntity = null;

if(entry.OriginalValues [prop.Name]是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(
外键{0}从[{1}:{2}]更改为[{3}:{4}]
prop.Name,
oldEntity.EntitySetName,oldEntity.EntityKeyValues [0],
newEntity.EntitySetName,newEntity.EntityKeyValues [0]);
}
}
}
}

希望这有助于更好地说明我正在努力实现的目标。欢迎任何输入。



谢谢!

解决方案

代码是正确的解决这个问题的方法:/ / / $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $ $

I'm overriding SaveChanges on my DbContext in order to implement an audit log. Working with many-to-many relationships or independent associations is relatively easy as EF creates ObjectStateEntries for any changes to those kinds of relationships.

I am using foreign key associations, and when a relationship between entities changes all you get is an ObjectStateEnty that says for example entity "Title" has "PublisherID" property changed. To a human this is obviously a foreign key in Title entity, but how do I determine this in runtime? Is there a way to translate this change to a "PublisherID" property to let's an EntityKey for the entity that foreign key represents?

I assume I'm dealing with entities that look like this:

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; }
}

There is also EF EntityConfiguration code that defines the relationship and foreign key:

public TitleConfiguration()
{
    HasOptional<Publisher>(t => t.Publisher).WithMany(
            p => p.Titles).HasForeignKey(t => t.PublisherID);
}

What I'm doing now seems a bit too complicated. I'm hoping there is more elegant way to achieve my goal. For every modified property from ObjectStateEntry I look through all ReferentialConstraints for current entity and see if any of those use it as a foreign key. The code below is called from 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]);
            }
        }
    }
}

I hope this helps to illustrate better what I am trying to achieve. Any input is welcome.

Thanks!

解决方案

Looks like my code is the right solution to this problem :/

I did end up using independent associations to avoid this problem altogether.

这篇关于实体框架:跟踪FK关联的更改的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆