【问题标题】:EF4.1 Code first one to many self referenceEF4.1 代码第一个一对多自引用
【发布时间】:2015-01-22 08:10:23
【问题描述】:

我正在尝试首先使用 EF4.1 代码进行一对多自引用。我的实体看起来像这样

public class Site
{
    public int Id { get; set; }
    public int? ParentId { get; set; }
    public string Sitename { get; set; }

    public virtual Site ChildSite { get; set; }
    public virtual ICollection<Site> ChildSites { get; set; }
}

在我的上下文类中,我这样做是为了进行自我引用

modelBuilder.Entity<Site>()
    .HasOptional(s => s.ChildSite)
    .WithMany(s => s.ChildSites)
    .HasForeignKey(s => s.ParentId);

但是当我尝试使用此代码向我的数据库添加一些虚拟数据时

var sites = new List<Site>
{
    new Site { ParentId = 0, Sitename = "Top 1" },
    new Site { ParentId = 0, Sitename = "Top 2" },
    new Site { ParentId = 0, Sitename = "Top 3" },
    new Site { ParentId = 0, Sitename = "Top 4" },
    new Site { ParentId = 1, Sitename = "Sub 1_5" },
    new Site { ParentId = 1, Sitename = "Sub 1_6" },
    new Site { ParentId = 1, Sitename = "Sub 1_7" },
    new Site { ParentId = 1, Sitename = "Sub 1_8" },
    new Site { ParentId = 2, Sitename = "Sub 2_9" },
    new Site { ParentId = 2, Sitename = "Sub 2_10" },
    new Site { ParentId = 2, Sitename = "Sub 2_11" },
    new Site { ParentId = 2, Sitename = "Sub 2_12" },
    new Site { ParentId = 3, Sitename = "Sub 3_13" },
    new Site { ParentId = 3, Sitename = "Sub 3_14" },
    new Site { ParentId = 3, Sitename = "Sub 3_15" },
    new Site { ParentId = 3, Sitename = "Sub 3_16" },
    new Site { ParentId = 4, Sitename = "Sub 4_17" },
    new Site { ParentId = 4, Sitename = "Sub 4_18" },
    new Site { ParentId = 4, Sitename = "Sub 4_19" },
    new Site { ParentId = 4, Sitename = "Sub 4_20" }
};
sites.ForEach(s => context.Sites.Add(s));
context.SaveChanges();

我得到这个错误:

无法确定主端 'Cms.Model.Site_ChildSite' 关系。多个添加的实体可能 具有相同的主键。

我在这里错过了什么?

编辑:

这是我的问题的解决方案。我从实体中删除了public virtual Site ChildSite { get; set; }

public class Site
{
    public int Id { get; set; }
    public int? ParentId { get; set; }
    public string Sitename { get; set; }

    public virtual ICollection<Site> ChildSites { get; set; }
}

然后我把 modelBuilder 改成了这个

modelBuilder.Entity<Site>()
    .HasMany(s => s.ChildSites)
    .WithOptional()
    .HasForeignKey(s => s.ParentId);

由于数据库的自动生成似乎不起作用,我继续像这样自己创建了表

int Id, not null, primary key
int ParentId, null
string Sitename, null

一切都如我所愿。

第二次编辑

以及让虚拟数据的自动生成工作的最后一步

var parent1 = new Site
{
    Sitename = "Top 1",
    ChildSites = new List<Site>
        {
            new Site {Sitename = "Sub 1_5"},
            new Site {Sitename = "Sub 1_6"},
            new Site {Sitename = "Sub 1_7"},
            new Site {Sitename = "Sub 1_8"}
        }
};

ect . . . 

context.Sites.Add(parent1);
context.SaveChanges();

请参阅下面的 Slauma 答案

【问题讨论】:

  • 您是否假设“Top 1”..“Top 4”的 ID 为 1 到 4?情况可能并非如此。 EF 无法假设,因此它不知道如何在插入时“连接”行。
  • 为什么你有两个属性ChildSiteChildSites
  • Matt >> 好的,我不知道,我尝试手动添加数据,它起作用了;o)

标签: entity-framework entity-framework-4.1 ef-code-first


【解决方案1】:

我有一个不同的模型,但我会发布我的情况所需的内容,它适用于 MVC 3 上的 EF4.1:

型号

public class Page
{
    public int Id { get; set; }
    public virtual string Title { get; set; }

    public int? ParentId { get; set; }
    public virtual Page Parent { get; set; }
    public virtual ICollection<Page> Children { get; set; }
}

数据库(使用流畅的 API)

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
     modelBuilder.Entity<Page>()
                 .HasOptional(s => s.Parent)
                 .WithMany(s => s.Children)
                 .HasForeignKey(s => s.ParentId);
}

初始化器

var page1 = new Page()
{
     Title = "title"
};

context.Pages.Add(page1);

var page2 = new Page()
{
      Parent = page1,
      Title = "title2",
};

context.Pages.Add(page2);

【讨论】:

    【解决方案2】:

    您的映射是正确的,您不需要删除ChildSite 属性。创建实体时不能使用外键属性,并“猜测”这些自动生成的数字在存储实体后将如何,并在存储的同一实体中使用这些数字。

    以下应该可以工作并在数据库中创建预期的行:

    var parent1 = new Site
    {
        Sitename = "Top 1",
        ChildSites = new List<Site>
            {
                new Site {Sitename = "Sub 1_5"},
                new Site {Sitename = "Sub 1_6"},
                new Site {Sitename = "Sub 1_7"},
                new Site {Sitename = "Sub 1_8"}
            }
    };
    var parent2 = new Site
    {
        Sitename = "Top 2",
        ChildSites = new List<Site>
            {
                new Site {Sitename = "Sub 2_9"},
                new Site {Sitename = "Sub 2_10"},
                new Site {Sitename = "Sub 2_11"},
                new Site {Sitename = "Sub 2_12"}
            }
    };
    var parent3 = new Site
    {
        Sitename = "Top 3",
        ChildSites = new List<Site>
            {
                new Site {Sitename = "Sub 3_13"},
                new Site {Sitename = "Sub 3_14"},
                new Site {Sitename = "Sub 3_15"},
                new Site {Sitename = "Sub 3_16"}
            }
    };
    var parent4 = new Site
    {
        Sitename = "Top 4",
        ChildSites = new List<Site>
            {
                new Site {Sitename = "Sub 4_17"},
                new Site {Sitename = "Sub 4_18"},
                new Site {Sitename = "Sub 4_19"},
                new Site {Sitename = "Sub 4_20"}
            }
    };
    context.Sites.Add(parent1);
    context.Sites.Add(parent2);
    context.Sites.Add(parent3);
    context.Sites.Add(parent4);
    context.SaveChanges();
    

    编辑

    如果数据库中存在外键表示的实体,您可以使用它 - 例如:

    var site = context.Sites.Single(s => s.Sitename == "Top 1");
    site.ParentId = 4; // set a parent for "Top 1"
    context.SaveChanges();
    

    添加一个新的孩子:

    var site = context.Sites.Single(s => s.Sitename == "Top 1");
    site.ChildSites.Add(new Site { Sitename = "Sub 1_9" });
    context.SaveChanges();
    

    移除一个孩子:

    var site = context.Sites.Single(s => s.Sitename == "Top 1");
    // works because of lazy loading
    var childToRemove = site.ChildSites.Single(s => s.Sitename == "Sub 1_9");
    site.ChildSites.Remove(childToRemove);
    context.SaveChanges();
    

    等等

    【讨论】:

    • 是的,按预期工作,但如果我无法编辑外键,我将如何在之后编辑单个站点?你能告诉我如何编辑“Top 1”网站吗?
    【解决方案3】:

    首先 - 不要指定 id - 我认为数据库负责为您的实体添加自动编号。

    尝试以这种方式填充您的数据库:

    var top1 = new Site { Sitename = "Top 1" };
    var sub15 = new Site { ChildSite = top1 , Sitename = "Sub 1_5" };
    var sub16 = new Site { ChildSite = top1 , Sitename = "Sub 1_6" };
    var sub17 = new Site { ChildSite = top1 , Sitename = "Sub 1_7" };
    ...
    context.Sites.Add(top1);
    
    var top2 = new Site { Sitename = "Top 2" };
    var sub29 = new Site { ChildSite = top2 , Sitename = "Sub 2_9" };
    var sub210 = new Site { ChildSite = top2 , Sitename = "Sub 2_10" };
    var sub211 = new Site { ChildSite = top2 , Sitename = "Sub 2_11" };
    ....
    context.Sites.Add(top2);
    
    ....
    context.SaveChanges();
    

    下一步:我认为您应该将 ChildSite 称为 ParentSite,这将使代码更易于阅读:

    var top1 = new Site { Sitename = "Top 1" };
    var sub15 = new Site { ParentSite = top1 , Sitename = "Sub 1_5" };
    var sub16 = new Site { ParentSite = top1 , Sitename = "Sub 1_6" };
    var sub17 = new Site { ParentSite = top1 , Sitename = "Sub 1_7" };
    ...
    context.Sites.Add(top1);
    
    var top2 = new Site { Sitename = "Top 2" };
    var sub29 = new Site { ParentSite = top2 , Sitename = "Sub 2_9" };
    var sub210 = new Site { ParentSite = top2 , Sitename = "Sub 2_10" };
    var sub211 = new Site { ParentSite = top2 , Sitename = "Sub 2_11" };
    ....
    context.Sites.Add(top2);
    
    ....
    context.SaveChanges();
    

    【讨论】:

      【解决方案4】:

      不知道为网站保留一组ChildSite 和一个ChildSites 的目的是什么。试试这个配置。

      public class Site
      {
          public int Id { get; set; }
          public int? ParentId { get; set; }
           public virtual  Site Parent { get; set; }
          public string Sitename { get; set; }
      
          public virtual ICollection<Site> ChildSites { get; set; }
      }
      
      modelBuilder.Entity<Site>()
          .HasOptional(s => s.Parent)
          .WithMany(s => s.ChildSites)
          .HasForeignKey(s => s.ParentId);
      

      编辑 当您插入时,您需要这样做。

       site s1=   new Site { ParentId = 0, Sitename = "Top 1" };
       site s2=   new Site { ParentId = 0, Sitename = "Top 2" };
       site s3=   new Site { ParentId = 0, Sitename = "Top 3" };
      //....
       site s4=  new Site { Parent = s1, Sitename = "Sub 1_5"};
      

      【讨论】:

      • 你说得对,我不需要公共虚拟站点 ChildSite { get;放;所以让我们摆脱它。当它消失时,我的自我参照关系应该是什么样子?
      • 你试过我回答中的模型配置了吗?
      • 是的,我做到了,但除了您称其为“站点父站点”与“站点子站点”之外,它与我的相同但是因为我不需要它在那里我应该用什么替换它: .HasOptional(s => s.Parent) ?
      • 看到我已经编辑了答案。我认为如果没有Parent 属性,您将无法以自己的方式完成任务。要在没有Parent 的情况下执行此操作,您需要为每个Site 调用context.SaveChanges();,并为下一个Site 获取Id ParentId
      • 请记住,上面的虚拟数据注入只是为了在实际应用程序中进行测试,我通常一次只添加一个站点。但是,您添加的代码仍然会出现同样的错误。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-18
      • 1970-01-01
      • 1970-01-01
      • 2015-07-18
      • 1970-01-01
      相关资源
      最近更新 更多