Ответ 1
Вот как найти все измененные отношения "многие ко многим" . Я реализовал код как методы расширения:
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 для доступа к ним.
Новая запись отношения создается, когда создается новая связь между двумя объектами, например, путем добавления сотрудника к коллекции 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();
}