【问题标题】:Tracking changes in Entity Framework for many-to-many relationships with behavior跟踪实体框架中与行为的多对多关系的变化
【发布时间】:2015-11-16 22:22:00
【问题描述】:

我目前正在尝试使用 Entity Framework 的 ChangeTracker 进行审计。我正在重写 DbContext 中的 SaveChanges() 方法,并为已添加、修改或删除的实体创建日志。这是该 FWIW 的代码:

public override int SaveChanges()
{
    var validStates = new EntityState[] { EntityState.Added, EntityState.Modified, EntityState.Deleted };
    var entities = ChangeTracker.Entries().Where(x => x.Entity is BaseEntity && validStates.Contains(x.State));
    var entriesToAudit = new Dictionary<object, EntityState>();
    foreach (var entity in entities)
    {
        entriesToAudit.Add(entity.Entity, entity.State);
    }
    //Save entries first so the IDs of new records will be populated
    var result = base.SaveChanges();
    createAuditLogs(entriesToAudit, entityRelationshipsToAudit, changeUserId);
    return result;
}

这对“普通”实体非常有效。但是,对于简单的多对多关系,我必须扩展此实现以包括“独立关联”,如this 梦幻般的 SO 答案中所述,它通过 ObjectContext 访问更改,如下所示:

private static IEnumerable<EntityRelationship> GetRelationships(this DbContext context, EntityState relationshipState, Func<ObjectStateEntry, int, object> getValue)
{
    context.ChangeTracker.DetectChanges();
    var objectContext = ((IObjectContextAdapter)context).ObjectContext;

    return objectContext
            .ObjectStateManager
            .GetObjectStateEntries(relationshipState)
            .Where(e => e.IsRelationship)
            .Select(
                e => new EntityRelationship(
                    e.EntitySet.Name,
                    objectContext.GetObjectByKey((EntityKey)getValue(e, 0)),
                    objectContext.GetObjectByKey((EntityKey)getValue(e, 1))));
}

实施后,这也很有效,但仅适用于使用联结表的多对多关系。在这里,我指的是关系不是由类/实体表示的情况,而只是一个具有两列的数据库表 - 每个外键一个列。

在我的数据模型中存在某些多对多关系,但是,该关系具有“行为”(属性)。在此示例中,ProgramGroup 是具有 Pin 属性的多对多关系:

public class Program
{
    public int ProgramId { get; set; }
    public List<ProgramGroup> ProgramGroups { get; set; }
}

public class Group
{
    public int GroupId { get; set; }
    public IList<ProgramGroup> ProgramGroups { get; set; }
}

public class ProgramGroup
{
    public int ProgramGroupId { get; set; }
    public int ProgramId { get; set; }
    public int GroupId { get; set; }
    public string Pin { get; set; }
}

在这种情况下,我在“正常”DbContext ChangeTracker 和 ObjectContext 关系方法中都没有看到 ProgramGroup 的变化(例如,如果 Pin 发生了变化)。但是,当我单步执行代码时,我可以看到更改发生在 ObjectContext 的 StateEntries 中,但它的条目有 IsRelationship=false,当然,这不符合 .Where(e =&gt; e.IsRelationship) 条件。

我的问题是为什么与行为的多对多关系没有出现在正常的 DbContext ChangeTracker 中,因为它由实际的类/实体表示,为什么它没有在 ObjectContext StateEntries 中标记为关系?另外,访问这些类型的更改的最佳做法是什么?

提前致谢。

编辑: 为了回应@FrancescCastells 的评论,可能没有明确定义ProgramGroup 的配置是问题的原因,我添加了以下配置:

public class ProgramGroupConfiguration : EntityTypeConfiguration<ProgramGroup>
{
    public ProgramGroupConfiguration()
    {
        ToTable("ProgramGroups");
        HasKey(p => p.ProgramGroupId);

        Property(p => p.ProgramGroupId).IsRequired();
        Property(p => p.ProgramId).IsRequired();
        Property(p => p.GroupId).IsRequired();
        Property(p => p.Pin).HasMaxLength(50).IsRequired();
    }

这是我的其他配置:

public class ProgramConfiguration : EntityTypeConfiguration<Program>
{
    public ProgramConfiguration()
    {
        ToTable("Programs");
        HasKey(p => p.ProgramId);
        Property(p => p.ProgramId).IsRequired();
        HasMany(p => p.ProgramGroups).WithRequired(p => p.Program).HasForeignKey(p => p.ProgramId);
    }
}

public class GroupConfiguration : EntityTypeConfiguration<Group>
{
    public GroupConfiguration()
    {
        ToTable("Groups");
        HasKey(p => p.GroupId);
        Property(p => p.GroupId).IsRequired();
        HasMany(p => p.ProgramGroups).WithRequired(p => p.Group).HasForeignKey(p => p.GroupId);
    }

实现这些后,EF 仍然不会在 ChangeTracker 中显示修改后的ProgramGroup

【问题讨论】:

  • 您能否将您的映射用于程序图?如果它说 IsRelationship=false 可能是关系配置有问题?
  • @FrancescCastells 我实际上并没有为我的 Program-ProgramGroup-Group 关系明确定义 EntityTypeConfiguration。 EntityFramework 按惯例处理它。
  • 好的。除非您确定它正确映射它,否则我建议明确映射它,只是为了尝试它。例如,它真的知道与没有导航属性的 Group 的关系吗?
  • @FrancescCastells 我用配置更新了我的帖子。实现之后,EF在ChangeTracker中依然没有更改ProgramGroup
  • 好的。您提到“独立关联”,根据定义,它们永远不会被标记为已修改,但是使用此映射程序组不是独立关联,因为它们指定了外键,所以这不是原因。恐怕我没有答案。

标签: c# entity-framework many-to-many


【解决方案1】:

虽然在实体关系建模理论中提到了“与属性的关系”的概念,但就实体框架而言,您的ProgramGroup 类是一个实体。您可能无意中使用x.Entity is BaseEntity 签入第一个代码 sn-p 将其过滤掉。

【讨论】:

    【解决方案2】:

    我认为问题在于您的 ProgramGroup 类的定义以及覆盖的 SaveChanges 方法。使用当前类的定义,EF 无法使用更改跟踪代理来捕获正在发生的更改。而不是 EF 依赖于快照更改检测,这是作为SaveChanges 方法的一部分完成的。由于您在覆盖方法的末尾调用了base.SaveChanges(),因此当您从ChangeTracker 请求更改时尚未检测到更改。

    您有两个选择 - 您可以在 SaveChanges 方法的开头调用 ChangeTracker.DetectChanges(); 或更改类的定义以支持更改跟踪代理。

    public class Program {
        public int ProgramId { get; set; }
        public virtual ICollection<ProgramGroup> ProgramGroups { get; set; }
    }
    
    public class Group {
        public int GroupId { get; set; }
        public virtual ICollection<ProgramGroup> ProgramGroups { get; set; }
    }
    

    创建变更跟踪代理的基本要求是:

    • 一个类必须声明为公共的

    • 不得密封类

    • 类不能是抽象的

    • 一个类必须有一个没有参数的公共或受保护的构造函数。

    • 表示关系“多”端的导航属性必须具有公共虚拟 get 和 set 访问器

    • 表示关系“多”端的导航属性必须定义为ICollection&lt;T&gt;

    【讨论】:

      【解决方案3】:

      实体框架通过在 CSDL 中没有用于连接表的实体集来表示多对多关系,而是通过映射来管理它。

      注意:实体框架仅在连接表不包含两个表的 PK 以外的任何列时才支持多对多关系

      你应该自己定义导航属性来解决这个问题。

      link 可以为您提供帮助。

      【讨论】:

        猜你喜欢
        • 2011-08-28
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-01-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多