Entity Framework 4.1+ отслеживание изменений отношений «многие ко многим» - PullRequest
29 голосов
/ 20 сентября 2011

Как я могу обнаружить изменения свойств ICollection <> (отношения «многие ко многим»)?

public class Company
{
    ...

    public virtual ICollection<Employee> Employees { get; set; }
}

using (DataContext context = new DataContext(Properties.Settings.Default.ConnectionString))
{
    Company company = context.Companies.First();
    company.Employees.Add(context.Employees.First());

    context.SaveChanges();
}

public class DataContext : DbContext
{
    public override int SaveChanges()
    {
        return base.SaveChanges();

        // Company's entity state is "Unchanged" in this.ChangeTracker
    }
}

Ответы [ 2 ]

83 голосов
/ 23 марта 2012

Вот как найти все измененные отношения «многие ко многим».Я реализовал код как методы расширения:

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();
} 
2 голосов
/ 13 ноября 2011

Я не могу дать вам точный код вашей ситуации, но могу вам сказать, что ваша ситуация будет упрощена в десять раз, если между сотрудниками и компанией будет разбит стол для соединения, просто чтобы разорвать отношения «многие ко многим».

...