【问题标题】:Entity Framework: Tracking changes to FK associations实体框架:跟踪 FK 关联的变化
【发布时间】:2011-08-28 06:45:05
【问题描述】:

我在我的 DbContext 上覆盖 SaveChanges 以实现审核日志。处理多对多关系或独立关联相对容易,因为 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 的每个修改的属性,我都会查看当前实体的所有引用约束,看看是否有任何一个将它用作外键。下面的代码是从 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]);
            }
        }
    }
}

我希望这有助于更好地说明我想要实现的目标。欢迎任何意见。

谢谢!

【问题讨论】:

  • 仅审核 ID 更改有什么问题?如果您直接在数据库中进行审计,您也将刚刚更改了 ID。顺便提一句。如果您不喜欢它,为什么要使用 FK 关联而不是独立关联?
  • 我试图为最终用户提供比从 b087929b-3221-4c43-916b-ad49066969c8 更改为 6372b8b6-1848-4f7a-ada0-d778a5682d67 的 PublisherID 更具描述性的内容。可能是指向实体的超链接。因此,无论是在为审计日志收集数据时,还是在显示审计日志时,我都需要弄清楚一个属性是否是外键以及它指向的实体的类型。
  • 我读了你关于独立协会的文章,感觉如果我选择走那条路,以后可能会遇到一些问题。最重要的是,如果我决定序列化具有适当外键值的 POCO 实体,可能会派上用场。有了独立的关联,我需要序列化某种实体图以使该数据有用。
  • 是的,Guid 对用户不是很友好,但如果主要问题是数据呈现,是否应该通过一些单独的查询来解决,以便在创建视图时将审计与数据结合起来?我不得不说我在我的大部分应用程序中都进行了审计,而我从来不需要这个——我只是审计了整行,所以我知道它对其他表是 FK。这就是为什么我想知道你为什么需要它的原因。您是否将每个更改的值作为单独的记录进行审核?
  • 我被要求“记录历史”,而不是完全维护该行的所有先前“版本”。我假设您对数据库中的每个实体都有一个单独的审计表,但我正在考虑使用一个审计表并以 XML 格式记录所有实体和记录更改。我还将在我的审计表中存储实体的表名和 PK。我试图找到一个通用的解决方案来跟踪依赖于可用元数据的关系变化。作为输入,我得到 ObjectContext 和 ObjectStateEntries 作为每个更改关系的输出,我想为新值和旧值生成 EntityKey。

标签: entity-framework poco ef-code-first entity-framework-4.1 code-first


【解决方案1】:

看起来我的代码是解决这个问题的正确方法:/

我确实最终使用了独立的关联来完全避免这个问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-10-04
    • 2011-04-23
    相关资源
    最近更新 更多