Вот как найти все измененные отношения «многие ко многим». Я реализовал код как методы расширения:
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 находятся в таблице соединений, и эта таблица соединений скрыта от объектной модели.
IA отслеживаются в EF, используя «записи отношений». Они похожи на объекты DbEntityEntry, которые вы получаете из DbContext.Entry, за исключением того, что они представляют собой взаимосвязь между двумя объектами, а не самим объектом. Записи взаимосвязей не отображаются в API-интерфейсе DbContext, поэтому вам необходимо перейти к ObjectContext для доступа к ним.
Новая запись отношения создается, когда создается новая связь между двумя объектами, например, путем добавления Сотрудник компании. Коллектив сотрудников. Это отношение находится в добавленном состоянии.
Аналогично, когда связь между двумя объектами удаляется, запись отношения помещается в состояние «Удалено».
Это означает, что для поиска измененного отношения «многие ко многим» (или фактически любые измененные 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();
}