繁体   English   中英

实体框架4.1+多对多关系更改跟踪

[英]Entity Framework 4.1+ many-to-many relationships change tracking

如何检测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
    }
}

这是查找所有已更改的多对多关系的方法。 我已经将代码实现为扩展方法:

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位于连接表中,并且该连接表对对象模型是隐藏的。

使用“关系条目”在EF中跟踪IA。 这些类似于您从DbContext.Entry获得的DbEntityEntry对象,除了它们表示两个实体之间的关系而不是实体本身。 关系条目未在DbContext API中公开,因此您需要下拉至ObjectContext才能访问它们。

当在两个实体之间创建新关系时(例如,通过将Employee添加到Company.Employees集合中),将创建一个新的关系条目。 此关系处于“已添加”状态。

同样,当两个实体之间的关系被删除时,该关系条目将进入Deleted状态。

这意味着要查找已更改的多对多关系(或实际上是任何已更改的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();
} 

我无法为您提供适合您情况的确切代码,但是我可以告诉您,通过在员工和公司之间建立一个联接器表来打破多对多关系,您的情况将简化十倍。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM