跟踪 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
。
我认为问题在于您的 Program
和 Group
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而言,你的ProgramGroup
class是一个实体.您可能在第一个代码片段中使用 x.Entity is BaseEntity
检查无意中过滤掉了它。
Entity Framework 通过在 CSDL 中没有用于连接 table 的实体集来表示多对多关系,而是通过映射来管理它。
注意: Entity framework 仅当连接 table 不包含除两个 tables
您必须自己定义导航 属性 才能解决这个问题。
这篇link可以帮到你。
我目前正在尝试使用 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
。
我认为问题在于您的 Program
和 Group
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而言,你的ProgramGroup
class是一个实体.您可能在第一个代码片段中使用 x.Entity is BaseEntity
检查无意中过滤掉了它。
Entity Framework 通过在 CSDL 中没有用于连接 table 的实体集来表示多对多关系,而是通过映射来管理它。
注意: Entity framework 仅当连接 table 不包含除两个 tables
您必须自己定义导航 属性 才能解决这个问题。
这篇link可以帮到你。