跟踪 Entity Framework 中与行为的多对多关系的变化

Tracking changes in Entity Framework for many-to-many relationships with behavior

我目前正在尝试使用 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;
}

这对 "normal" 个实体非常有用。但是,对于简单的多对多关系,我不得不扩展此实现以包括 this 中所述的 "Independent Associations" 非常棒的 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))));
}

一旦实施,这也很有效,但仅适用于使用联结 table 的多对多关系。通过这种方式,我指的是关系不是由 class/entity 表示的情况,而只是一个具有两列的数据库 table - 每个外键一列。

我的数据模型中存在某些多对多关系,但是,该关系具有 "behavior"(属性)。在此示例中,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; }
}

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

我的问题是,为什么多对多关系与行为没有出现在正常的 DbContext ChangeTracker 中,因为它是由实际的 class/entity 表示的,为什么它没有在 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

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

请求更改时,尚未检测到更改

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

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

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

  • A class 必须声明为 public

  • 一个class一定不能封

  • 一个class不能是抽象的

  • A class 必须有一个 public 或没有参数的受保护构造函数。

  • 表示关系 "many" 结束的导航 属性 必须具有 public 虚拟获取和设置访问器

  • 表示关系 "many" 结束的导航 属性 必须定义为 ICollection<T>

虽然在实体关系建模理论中提到了"relationship with attributes"的概念,但就Entity Framework而言,你的ProgramGroupclass是一个实体.您可能在第一个代码片段中使用 x.Entity is BaseEntity 检查无意中过滤掉了它。

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

注意: Entity framework 仅当连接 table 不包含除两个 tables

您必须自己定义导航 属性 才能解决这个问题。

这篇link可以帮到你。