【问题标题】:how to annotate a parent-child relationship with Code-First如何使用 Code-First 注释父子关系
【发布时间】:2011-05-26 14:16:34
【问题描述】:

当使用实体框架代码优先库的 CTP 5(如 here 宣布的那样)时,我正在尝试创建一个映射到一个非常简单的层次结构表的类。

这是构建表的 SQL:

CREATE TABLE [dbo].[People]
(
 Id  uniqueidentifier not null primary key rowguidcol,
 Name  nvarchar(50) not null,
 Parent  uniqueidentifier null
)
ALTER TABLE [dbo].[People]
 ADD CONSTRAINT [ParentOfPerson] 
 FOREIGN KEY (Parent)
 REFERENCES People (Id)

这是我希望自动映射回该表的代码:

class Person
{
    public Guid Id { get; set; }
    public String Name { get; set; }
    public virtual Person Parent { get; set; }
    public virtual ICollection<Person> Children { get; set; }
}

class FamilyContext : DbContext
{
    public DbSet<Person> People { get; set; }
}

我在 app.config 文件中设置了连接字符串:

<configuration>
  <connectionStrings>
    <add name="FamilyContext" connectionString="server=(local); database=CodeFirstTrial; trusted_connection=true" providerName="System.Data.SqlClient"/>
  </connectionStrings>
</configuration>

最后我尝试使用该类来添加父实体和子实体,如下所示:

static void Main(string[] args)
{
    using (FamilyContext context = new FamilyContext())
    {
        var fred = new Person
        {
            Id = Guid.NewGuid(),
            Name = "Fred"
        };
        var pebbles = new Person
        {
            Id = Guid.NewGuid(),
            Name = "Pebbles",
            Parent = fred
        };
        context.People.Add(fred);
        var rowCount = context.SaveChanges();
        Console.WriteLine("rows added: {0}", rowCount);
        var population = from p in context.People select new { p.Name };
        foreach (var person in population)
            Console.WriteLine(person);
    }
}

这里显然缺少一些东西。我得到的例外是:

列名“PersonId”无效。

我了解约定优于配置的价值,我和我的团队对摆脱 edmx / 设计师噩梦的前景感到兴奋 --- 但似乎没有关于约定是什么的明确文档。 (对于单数类名,我们只是幸运地使用了复数表名的概念)

对于如何使这个非常简单的示例落实到位的一些指导,我们将不胜感激。

更新: 将 People 表中的列名从 Parent 更改为 PersonId 允许添加 fred 继续进行。但是,您会注意到 pebbles 已添加到 fred 的 Children 集合中,因此我希望在添加 Fred 时也会将鹅卵石添加到数据库中,但事实并非如此。这是一个非常简单的模型,所以我有点沮丧,因为在将几行输入数据库时​​应该涉及这么多的猜测工作。

【问题讨论】:

    标签: entity-framework code-first


    【解决方案1】:

    您需要下拉到 fluent API 以实现您想要的架构(数据注释不会这样做)。确切地说,您有一个 Independent One-to-Many Self Reference Association,它还具有外键列的自定义名称 (People.Parent)。以下是使用 EF Code First 应该如何完成的:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Person>()
                    .HasOptional(p => p.Parent)
                    .WithMany(p => p.Children)
                    .IsIndependent()
                    .Map(m => m.MapKey(p => p.Id, "ParentID"));
    }
    

    但是,这会引发带有此消息的InvalidOperationException序列包含多个匹配元素。根据 Steven 在他的回答中提到的链接,这听起来是一个 CTP5 错误。

    您可以使用一种解决方法,直到此错误在 RTM 中得到修复,即接受 FK 列的默认名称 PersonID。为此,您需要稍微更改架构:

    CREATE TABLE [dbo].[People]
    (
         Id  uniqueidentifier not null primary key rowguidcol,
         Name  nvarchar(50) not null,
         PersonId  uniqueidentifier null
    )
    ALTER TABLE [dbo].[People] ADD CONSTRAINT [ParentOfPerson] 
    FOREIGN KEY (PersonId) REFERENCES People (Id)
    GO
    ALTER TABLE [dbo].[People] CHECK CONSTRAINT [ParentOfPerson]
    GO
    

    然后使用这个 fluent API 将您的数据模型与 DB Schema 匹配:

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Person>()
                    .HasOptional(p => p.Parent)
                    .WithMany(p => p.Children)
                    .IsIndependent();
    }
    

    添加一个包含 Child 的新 Parent 记录:

    using (FamilyContext context = new FamilyContext())
    {
        var pebbles = new Person
        {
            Id = Guid.NewGuid(),
            Name = "Pebbles",                    
        };
        var fred = new Person
        {
            Id = Guid.NewGuid(),
            Name = "Fred",
            Children = new List<Person>() 
            { 
                pebbles
            }
        };                
        context.People.Add(fred);               
        context.SaveChanges();                                
    }
    

    添加一个包含父项的新子记录:

    using (FamilyContext context = new FamilyContext())
    {
        var fred = new Person
        {
            Id = Guid.NewGuid(),
            Name = "Fred",                
        };
        var pebbles = new Person
        {
            Id = Guid.NewGuid(),
            Name = "Pebbles",
            Parent = fred
        };
        context.People.Add(pebbles);
        var rowCount = context.SaveChanges();                                
    }
    

    两个代码具有相同的效果,即添加一个新的父级 (Fred) 和一个子级 (Pebbles)。

    【讨论】:

    • 这是一个很好的答案。谢谢。现在在 OP 的代码中,我没有明确地将鹅卵石添加到上下文中,而是假设她将通过连接到 Fred(通过将她的 Parent 属性设置为 fred 实例)而被添加到数据库中工作,但我必须明确添加鹅卵石。你这是预期的行为吗?
    • 在这个示例代码中,我们将 Children 属性声明为 ICollection,但我们从未实例化它。是否应该有一个默认构造函数来做到这一点?
    • 没问题,请参阅我对您的第一个问题的更新答案。此外,您不必在类构造函数中特别初始化 Children 以将其标记为虚拟,但仍建议您这样做,以免您有任何机会遇到 NullReferenceException。
    • 感谢您的更新,可惜我不能再次为您的答案投票。
    【解决方案2】:

    在 Entity Framework 6 中,您可以这样做,注意 public Guid? ParentId { get; set; }。外键必须可以为空才能正常工作。

    class Person
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public Guid? ParentId { get; set; }
        public virtual Person Parent { get; set; }
        public virtual ICollection<Person> Children { get; set; }
    }
    

    https://stackoverflow.com/a/5668835/3850405

    【讨论】:

      【解决方案3】:

      它应该使用如下映射来工作:

      class FamilyContext : DbContext
      {
          public DbSet<Person> People { get; set; }
      
          protected override void OnModelCreating(ModelBuilder builder)
          {
              builder.Entity<Person>().HasMany(x => x.Children).WithMany().Map(y =>
                  {
                      y.MapLeftKey((x => x.Id), "ParentID");
                      y.MapRightKey((x => x.Id), "ChildID");
      
                  });
          }
      }
      

      但是,这会引发异常:序列包含多个匹配元素。 显然这是一个错误。

      查看这个帖子和@shichao 问题的答案:http://blogs.msdn.com/b/adonet/archive/2010/12/06/ef-feature-ctp5-fluent-api-samples.aspx#10102970

      【讨论】:

      • 这个流畅的 API 代码适用于使用连接表的多对多关联,而问题是关于 One-to-Many Self Reference Association。谢谢。
      【解决方案4】:
          class Person
      { 
          [key()]
          public Guid Id { get; set; }
          public String Name { get; set; }
          [ForeignKey("Children")]
          public int? PersonId {get; set;} //Add ForeignKey
          public virtual Person Parent { get; set; }
          public virtual ICollection<Person> Children { get; set; }
      }
      
      builder.Entity<Menu>().HasMany(m => m.Children)
                              .WithOne(m => m.Parent)
                              .HasForeignKey(m => m.PersonId);
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多