两个表之间的多个关系

时间:2013-03-25 12:50:27

标签: c# ef-code-first

我想在EF Code First中存储两个类。建筑物有维护者,以及在那里工作的人员名单。 mainainer不需要在建筑物内工作。

在我第一次尝试时,我只是

public class Person
    {
        public virtual Guid Id { get; set; }
        public virtual string Name { get; set; }

    }

public class Building
{
    public virtual  Guid Id { get; set; }
    public virtual List<Person> WorksHere { get; set; }

    public virtual Person MaintainedBy { get; set; }
    public virtual String Address { get; set; }
}

这给了我两个带有基本属性的表,一个关于People的Building_Id和一个关于建筑物的MaintainedBy_Id。

当我运行测试程序时

        using (TestContext tc = new TestContext())
        {
            Person m1 = tc.persons.Create();
            m1.Name = "maintainB1";
            m1.Id = Guid.NewGuid();

            Person m2 = tc.persons.Create();
            m2.Name = "maintainB2";
            m2.Id = Guid.NewGuid();

            Building b1 = tc.buildings.Create();
            b1.Address = "building1";
            b1.Id = Guid.NewGuid();
            tc.buildings.Add(b1);

            Building b2 = tc.buildings.Create();
            b2.Address = "building1";
            b2.Id = Guid.NewGuid();
            tc.buildings.Add(b2);

            b1.MaintainedBy = m1;
            b2.MaintainedBy = m2;

            if (b1.WorksHere == null) b1.WorksHere = new List<Person>();
            if (b2.WorksHere == null) b2.WorksHere = new List<Person>();


            b1.WorksHere.AddRange(new List<String>() { "e11", "e12", "e13" }.Select(s =>
            {
                Person p = new Person();
                p.Id = Guid.NewGuid();
                p.Name = s;
                return p;
            }));

            b2.WorksHere.AddRange(new List<String>() { "e21", "e22", "e23" }.Select(s =>
            {
                Person p = new Person();
                p.Id = Guid.NewGuid();
                p.Name = s;
                return p;
            }));


            b1.WorksHere.Add(m2);
            b2.WorksHere.Add(m1);
            tc.SaveChanges();
        }

    }

我得到一个异常:“保存不公开其关系的外键属性的实体时发生错误.EntityEntries属性将返回null,因为无法将单个实体标识为异常的来源。通过在实体类型中公开外键属性可以使保存变得更容易。有关详细信息,请参阅InnerException。“

带有不良情绪: “无法确定依赖操作的有效排序。由于外键约束,模型要求或存储生成的值,可能存在依赖关系。”

我不想在我的Poco上公开任何更多的属性,因为,它们是Poco的,我对这些属性毫无用处。如果我必须满足Code First模型生成,那么就必须这样做,但如果可能的话,我希望在映射类中远离我的Poco。

我该如何解决这个问题?

2 个答案:

答案 0 :(得分:2)

因此,出现此问题的原因是您的实体之间存在循环关系,导致EF在尝试解析单SaveChanges次调用中的所有插入时放弃,并引发您看到的异常。

要理解为什么它无法处理这种情况,让我们考虑一下在尝试保存实体时数据库中会发生什么。

使用您的代码,您可以通过在调用SaveChanges之前注释掉最后一行来使其无误地运行,但是m1人员不会在构建b2时工作,所以这不是你想要的。

b1.WorksHere.Add(m2);
//b2.WorksHere.Add(m1); <-- When this is removed it works.. 
tc.SaveChanges();

但是,EF可以通过在数据库中创建以下插入来运行它:

  1. 插入人m1。将FK保留为buildings表为null,因为m1无处可用。
  2. 插入建筑物'b1'。使用'm1'的id作为FK,因为m1维护b1
  3. 插入人m2。使用b1的ID作为FK,因为b1m2的工作地点。
  4. 插入建筑物b2。使用m2的ID作为FK,因为m2维护b2
  5. 现在,当您在m1中加入使b2工作的行时,很容易理解为什么它不起作用。
    在上面的第一个插入中,EF无法将FK保留为null,因为您告诉它它需要指向一个建筑物,但该建筑尚未插入,因此它无法创建FK指向回到它。
    当实体具有循环依赖性时,这始终是EF中的问题。当两者相互依赖时,无法在单个提交中创建插入。

    问题的解决方案只是拨打SaveChanges两次。如果您在m1b2之前调用它,然后再次使用,那么您将获得正确的行为。

    b1.WorksHere.Add(m2);
    tc.SaveChanges(); <-- Create inserts. FK in m1 is null because he works nowhere yet.
    b2.WorksHere.Add(m1);
    tc.SaveChanges(); <-- Updates FK in m1 to point to b2.
    

    实体框架中的未来支持
    似乎这个问题将在EF的未来版本中得到解决。

    从ORM中得到它应该能够处理插入父项,插入子项然后使用新创建的子ID更新父项是合理的。

    您可以阅读有关它的更多信息,并投票赞成要在Microsoft Connect上实施的功能。

    EF CodePlex site.

    还有一些信息

答案 1 :(得分:0)

尝试映射关系。在从DbContext派生的上下文中,重写OnModelCreating方法:

protected override void OnModelCreating(DbModelBuilder mb)
{
    mb.Entity<Building>()
        .HasMany(m => m.LivesHere)
        .WithRequired()
        .Map(n => n.MapKey("Home_Id"));
}

我也会尝试使用[Key]来表示实体中的主键,但我不确定你是否必须这样做。

像这样:

public class Building
{
    [Key]
    public virtual  Guid Id { get; set; }
    public virtual List<Person> WorksHere { get; set; }

    public virtual Person MaintainedBy { get; set; }
    public virtual String Address { get; set; }
}

我现在无法尝试,但我希望它有所帮助。