как аннотировать отношения родитель-потомок с помощью Code-First

При использовании CTP 5 библиотеки Code-first Entity Framework (как было объявлено здесь) Я пытаюсь создать класс, который сопоставляется с очень простой иерархической таблицей.

Вот 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 добавлено в коллекцию Children fred, поэтому я ожидал, что камешки также будут добавлены в базу данных, когда был добавлен Фред, но этого не произошло. Это очень простая модель, поэтому я более чем обескуражен тем, что для ввода пары строк в базу данных требуется столько догадок.


person Ralph Shillington    schedule 16.12.2010    source источник


Ответы (3)


Вам нужно перейти к свободному 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 с сообщением Последовательность содержит более одного совпадающего элемента. что звучит как ошибка 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 сопоставит вашу модель данных со схемой БД:

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

Оба кода имеют одинаковый эффект и добавляют нового родителя (Фреда) с дочерним элементом (Галька).

person Morteza Manavi    schedule 16.12.2010
comment
Это отличный ответ. Спасибо. Теперь в коде ОП я не добавлял камешки в контекст явно, но предполагал, что она будет добавлена ​​в базу данных в силу того, что она подключена к Фреду (путем установки ее свойства Parent для экземпляра fred). Это не работать, а скорее мне пришлось добавить камешки в явном виде. Вы ожидаете такого поведения? - person Ralph Shillington; 16.12.2010
comment
В этом примере кода мы объявляем свойство Children как ICollection‹Person›, но никогда не создаем его экземпляр. Должен ли быть конструктор по умолчанию для этого? - person Ralph Shillington; 16.12.2010
comment
Нет проблем, смотрите мой обновленный ответ на ваш первый вопрос. Кроме того, вам не нужно инициализировать Children в конструкторе класса специально, чтобы пометить его как виртуальный, но все же рекомендуется, чтобы вы случайно не столкнулись с NullReferenceException. - person Morteza Manavi; 16.12.2010
comment
Спасибо за обновление, жаль, что я не могу снова проголосовать за ваш ответ. - person Ralph Shillington; 17.12.2010

В 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

person Ogglas    schedule 15.08.2018

Он должен работать с использованием сопоставления, как показано ниже:

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

person Steven K.    schedule 16.12.2010
comment
Этот плавный код API предназначен для ассоциаций «многие ко многим» с использованием таблицы соединений, а вопрос касается One-to-Many Self Reference Association. Спасибо. - person Morteza Manavi; 16.12.2010