如何使用Code-First注释父子关系

时间:2010-12-16 14:27:31

标签: entity-framework code-first

使用实体框架代码优先库的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文件中设置了connectionstring,如下所示:

<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的儿童集合中,因此我希望在添加Fred时将鹅卵石添加到数据库中,但事实并非如此。这是一个非常简单的模型,所以我有点沮丧的是,在将几行输入数据库时​​应该进行这么多的猜测工作。

4 个答案:

答案 0 :(得分:13)

您需要下拉到流畅的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此消息 Sequence包含多个匹配元素。根据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

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

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

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

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();                                
}

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

答案 1 :(得分:0)

它应该使用如下的映射:

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");

            });
    }
}

然而,抛出异常:Sequence包含多个匹配元素。 显然这是一个错误。

请参阅此主题以及@shichao问题的答案:http://blogs.msdn.com/b/adonet/archive/2010/12/06/ef-feature-ctp5-fluent-api-samples.aspx#10102970

答案 2 :(得分:0)

在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 :(得分:0)

    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);