Как правильно моделировать самоссылающиеся многородительские отношения, используя структуру сущностей

Я пытаюсь смоделировать следующий класс лиц, ссылающихся на себя, в EF6.

public class Person
{
    public int ID { get; set; }
    public string Name { get; set; }

    public int? MotherId { get; set; }
    public Person Mother { get; set; }

    public int? FatherId { get; set; }
    public Person Father { get; set; }

    public virtual ICollection<Person> Children { get; set; }
}

И мой DbContext выглядит так:

public virtual DbSet<Person> People { get; set; }

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Entity<Person>()
        .HasOptional(m => m.Mother)
        .WithMany(c => c.Children)
        .HasForeignKey(m => m.MotherId);

     modelBuilder.Entity<Person>()
        .HasOptional(f => f.Father)
        .WithMany(c => c.Children)
        .HasForeignKey(f => f.FatherId);
}

При попытке добавить человека в базу данных с помощью следующего кода:

db.People.Add(new Person { Name = "Jane Doe" });

Я получаю эту ошибку:

В EntityFramework.dll возникло необработанное исключение типа «System.InvalidOperationException».

Дополнительная информация: последовательность не содержит совпадающих элементов.

Что означает эта ошибка и как ее исправить? Есть ли, при желании, лучший способ смоделировать этот объект (например, с использованием подкласса Мать:Человек и Отец:Человек)?


person Jens Ehrich    schedule 16.04.2015    source источник


Ответы (2)


Я придумал следующее решение, которое создает чистую базу данных и обеспечивает большую гибкость при добавлении отношений:

public interface IParent
{
    ICollection<Person> Children { get; set; }
}

public class Person
{
    public int ID { get; set; }
    public string Name { get; set; }

    public int? MotherId { get; set; }
    public Female Mother { get; set; }

    public int? FatherId { get; set; }
    public Male Father { get; set; }
}

public class Male : Person, IParent
{
    public virtual ICollection<Person> Children { get; set; }
}

public class Female : Person, IParent
{
    public virtual ICollection<Person> Children { get; set; }
}

DbContext содержит только:

public virtual DbSet<Person> People { get; set; }
public virtual DbSet<Female> Females { get; set; }
public virtual DbSet<Male> Males { get; set; }

И результирующая база данных выглядит так:

ID  Name    MotherId    FatherId    Discriminator
1   Jane    NULL        NULL        Female
2   John    NULL        NULL        Male
3   Jimmy   1           2           Male
4   Jenn    1           2           Female

Это решение также обеспечивает гибкость добавления отношений несколькими способами:

mom.Children.Add(son); // or
son.Mother = mom;
person Jens Ehrich    schedule 16.04.2015
comment
как указать FK? - person Ewan; 16.04.2015
comment
Вам не нужно — EF по умолчанию использует PK связанного объекта, и по соглашению это свойство с именем ID или ClassNameID. - person Jens Ehrich; 16.04.2015

Я бы сделал следующее. Хотя это может не соответствовать вашему требованию «In Entity Framework».

public class Person
{
    public int ID { get; set; }
    public string Name { get; set; }

    public int? MotherId { get; set; }
    public int? FatherId { get; set; }
}

public Class RelationFinder
{
    Public Person GetMother(IEnumerable<Person> people, Person child)
    {
        return people.FirstOrDefault(p=>p.Id = child.MotherId);
    }

    Public Person GetFather(...

    Public IEnumerable GetChildern(...
}
person Ewan    schedule 16.04.2015
comment
Это хороший вариант, но я стараюсь придерживаться встроенной магии EF и писать как можно меньше дополнительного кода. - person Jens Ehrich; 16.04.2015
comment
к сожалению, магия EF не слишком хорошо работает с обнуляемыми FK и самоссылками. может быть, вы измените IEnumerables на IQueryables, и вы получите тот же эффект? - person Ewan; 16.04.2015