Подтвердить что ты не робот

Entity Framework 4.1+ отношения "многие ко многим" меняют отслеживание

Как я могу обнаружить изменения свойств 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 entity state is "Unchanged" in this.ChangeTracker
    }
}
4b9b3361

Ответ 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();
} 

Ответ 2

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