以下是如何查找所有已更改的多对多关系。我已将代码实现为扩展方法:
public static class IaExtensions
{
public static IEnumerable<Tuple<object, object>> GetAddedRelationships(
this DbContext context)
{
return GetRelationships(context, EntityState.Added, (e, i) => e.CurrentValues[i]);
}
public static IEnumerable<Tuple<object, object>> GetDeletedRelationships(
this DbContext context)
{
return GetRelationships(context, EntityState.Deleted, (e, i) => e.OriginalValues[i]);
}
private static IEnumerable<Tuple<object, object>> 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 => Tuple.Create(
objectContext.GetObjectByKey((EntityKey)getValue(e, 0)),
objectContext.GetObjectByKey((EntityKey)getValue(e, 1))));
}
}
一些解释。多对多关系在 EF 中表示为独立关联 (IA)。这是因为关系的外键没有在对象模型中的任何地方公开。在数据库中,FK 位于连接表中,并且该连接表对对象模型是隐藏的。
IAs 在 EF 中使用“关系条目”进行跟踪。它们与从 DbContext.Entry 获取的 DbEntityEntry 对象类似,只不过它们表示两个实体之间的关系而不是实体本身。关系条目未在 DbContext API 中公开,因此您需要下拉至 ObjectContext 才能访问它们。
当两个实体之间创建新关系时,就会创建一个新关系条目,例如通过将 Employee 添加到 Company.Employees 集合。该关系处于已添加状态。
同样,当删除两个实体之间的关系时,关系条目将进入“已删除”状态。
这意味着要查找更改的多对多关系(或实际上任何更改的 IA),我们需要查找添加和删除的关系条目。这就是 GetAddedRelationships 和 GetDeletedRelationships 的作用。
一旦我们有了关系条目,我们就需要理解它们。为此,您需要了解一些内幕知识。已添加(或未更改)关系条目的 CurrentValues 属性包含两个值,它们是关系两端实体的 EntityKey 对象。同样,但令人烦恼的略有不同,已删除关系条目的 OriginalValues 属性包含已删除关系两端实体的 EntityKey 对象。
(是的,这太可怕了。请不要责怪我——这已经是我时代之前的事情了。)
CurrentValues/OriginalValues 的差异是我们将委托传递给 GetRelationships 私有方法的原因。
一旦我们有了 EntityKey 对象,我们就可以使用 GetObjectByKey 来获取实际的实体实例。我们将它们作为元组返回,然后您就得到了它。
这里有一些实体、上下文和初始化器,我用来测试它。 (注意——测试并不广泛。)
public class Company
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Employee> Employees { get; set; }
public override string ToString()
{
return "Company " + Name;
}
}
public class Employee
{
public int Id { get; set; }
public string Name { get; set; }
public virtual ICollection<Company> Companies { get; set; }
public override string ToString()
{
return "Employee " + Name;
}
}
public class DataContext : DbContext
{
static DataContext()
{
Database.SetInitializer(new DataContextInitializer());
}
public DbSet<Company> Companies { get; set; }
public DbSet<Employee> Employees { get; set; }
public override int SaveChanges()
{
foreach (var relationship in this.GetAddedRelationships())
{
Console.WriteLine(
"Relationship added between {0} and {1}",
relationship.Item1,
relationship.Item2);
}
foreach (var relationship in this.GetDeletedRelationships())
{
Console.WriteLine(
"Relationship removed between {0} and {1}",
relationship.Item1,
relationship.Item2);
}
return base.SaveChanges();
}
}
public class DataContextInitializer : DropCreateDatabaseAlways<DataContext>
{
protected override void Seed(DataContext context)
{
var newMonics = new Company { Name = "NewMonics", Employees = new List<Employee>() };
var microsoft = new Company { Name = "Microsoft", Employees = new List<Employee>() };
var jim = new Employee { Name = "Jim" };
var arthur = new Employee { Name = "Arthur" };
var rowan = new Employee { Name = "Rowan" };
newMonics.Employees.Add(jim);
newMonics.Employees.Add(arthur);
microsoft.Employees.Add(arthur);
microsoft.Employees.Add(rowan);
context.Companies.Add(newMonics);
context.Companies.Add(microsoft);
}
}
这是使用它的示例:
using (var context = new DataContext())
{
var microsoft = context.Companies.Single(c => c.Name == "Microsoft");
microsoft.Employees.Add(context.Employees.Single(e => e.Name == "Jim"));
var newMonics = context.Companies.Single(c => c.Name == "NewMonics");
newMonics.Employees.Remove(context.Employees.Single(e => e.Name == "Arthur"));
context.SaveChanges();
}