Вот как найти все изменившиеся отношения «многие ко многим». Я реализовал код как методы расширения:
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, за исключением того, что они представляют связь между двумя сущностями, а не саму сущность. Записи отношений не отображаются в DbContext API, поэтому для доступа к ним необходимо перейти к ObjectContext.
Новая запись отношения создается при создании новой связи между двумя сущностями, например, путем добавления сотрудника в коллекцию Company.Employees. Эта связь находится в состоянии «Добавлено».
Аналогичным образом, когда связь между двумя объектами удаляется, запись связи переводится в состояние «Удалено».
Это означает, что для поиска измененных отношений "многие ко многим" (или фактически любого измененного IA) нам необходимо найти добавленные и удаленные записи отношений. Это то, что делают GetAddedRelationships и GetDeletedRelationships.
Когда у нас есть записи отношений, нам нужно разобраться в них. Для этого вам нужно знать частичку инсайдерских знаний. Свойство CurrentValues записи отношения Added (или Unchanged) содержит два значения, которые являются объектами 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();
}
person
Arthur Vickers
schedule
23.03.2012
ObjectStateManager
возвращает больше записей, чемChangeTracker
из DbContext, особенно также записи типа Relationship. Например:((IObjectContextAdapter)DbContext).ObjectContext.ObjectStateManager .GetObjectStateEntries(EntityState.Added)
возвращает одинObjectStateEntry
, который представляет добавленную связь в вашем примере. Но я не могу понять, как действовать дальше, интересные данные в этой записи (родительская и добавленная дочерняя сущность) видны в отладчике, но все частные / внутренние ... - person Slauma   schedule 21.09.2011