Вот как найти все измененные отношения «многие ко многим».Я реализовал код как методы расширения:
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 записи отношения Deleted содержит объекты 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();
}