Как правилно да моделираме самореферираща връзка с множество родители с помощта на рамка на обект

Опитвам се да моделирам следния саморефериращ клас на човек в 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" });

Получавам тази грешка:

Възникна необработено изключение от тип „System.InvalidOperationException“ в EntityFramework.dll

Допълнителна информация: Последователността не съдържа съответстващ елемент

Какво означава тази грешка и как да я коригирам? По избор, има ли по-добър начин за моделиране на този обект (напр.: използване на подклас на Mother:Person и Father:Person)?


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
за съжаление магията на EFs не работи твърде добре с nullable FK и саморефериране. може би ако промените IEnumerables на IQueryables, можете да получите същия ефект? - person Ewan; 16.04.2015