Как настроить несколько однозначных отношений в Entity Framework Core?

У меня есть две сущности: Parent и Child; каждый родитель может иметь не более двух дочерних ссылок. Я настроил свои объекты следующим образом:

class Parent
{
    [Key]
    public int ParentId { get; set; }

    public int PrimaryChildId{ get; set; }
    public Child PrimaryChild { get; set; }

    public int SecondaryChildId { get; set; }
    public Child? SecondaryChild { get; set; }
    // remaining properties
}

class Child 
{
    [Key]
    public int ChildId { get; set; }

    public int ParentId { get; set; }
    public Parent Parent {get; set; }

    // remaining child properties 
}

В DbContext.OnModelCreating у меня такой код:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Parent>(builder =>
    {
        builder.HasOne(p => p.PrimaryChild);
        builder.HasOne(p => p.SecondaryChild);
    });
}

Этого недостаточно для достижения того, чего я пытаюсь достичь здесь. Я получаю сообщение об ошибке:

Невозможно определить взаимосвязь, представленную свойством навигации «Child.Parent» типа «Parent». Либо настройте связь вручную, либо проигнорируйте это свойство с помощью атрибута «[NotMapped]» или с помощью EntityTypeBuilder.Ignore в «OnModelCreating»

Я пытался установить связь из объекта Child, но получаю разные ошибки, потому что это заставляет меня устанавливать две связи для одного и того же свойства. Я не хочу, чтобы у моего ребенка было два свойства навигации, если я знаю, что одновременно будет использоваться только одно, поскольку это может сбить с толку модель.

Я немного искал в Интернете, но мне не удалось найти отношения, которые установлены таким образом.


person Jesse    schedule 12.02.2020    source источник
comment
Я думаю, что лучше использовать от одного до многих. Потому что у вас один родитель и много детей. Теоретически делать это один на один нехорошо.   -  person Anton    schedule 12.02.2020
comment
Как EF узнает, какой ребенок является основным, а какой второстепенным? Я думаю, у вас здесь отношения «многие к одному». Вы можете применять свои собственные правила в логике домена. Вы можете создать собственное свойство PrimaryChild, чтобы получить первого потомка в коллекции или что-то подобное.   -  person chadnt    schedule 12.02.2020
comment
@ Антон, да, я начинаю думать именно так. Я всегда мог применить ограничение на бизнес-уровне, и тогда это действительно упростило бы задачу в будущем, если требования изменятся. Мне любопытно, возможно ли это, поэтому я думаю, что оставлю вопрос активным. Спасибо за вклад.   -  person Jesse    schedule 12.02.2020
comment
Согласен @chadnt. Когда я проектировал это, я думал только о бизнес-требованиях, и если бы я делал все управление базой данных вручную, это, безусловно, можно было бы сделать, но я не думаю, что для этого стоит отказываться от мощности ORM.   -  person Jesse    schedule 12.02.2020
comment
Но вы можете попытаться удалить родительский и родительский идентификаторы из дочернего элемента и не использовать конструктор моделей. Возможно, ядро ​​EF создаст два взаимно однозначно вручную.   -  person Anton    schedule 12.02.2020
comment
@Anton, похоже, выдает ошибку каскадного удаления. Я не уверен, что это связано с другой проблемой или с этими отношениями. Я думаю, что лучше всего согласиться с вашим первоначальным предложением. Это не стоит усилий для создания излишне (за пределами бизнес-требований) более строгой базы данных. Будет легче обеспечить соблюдение правила на бизнес-уровне.   -  person Jesse    schedule 12.02.2020
comment
@ Джесси хорошо. Я просто устроил мозговой штурм :)   -  person Anton    schedule 12.02.2020


Ответы (1)


Я пробовал что-то подобное в течение последних нескольких дней, и после того, как попробовал всевозможные аннотации к данным и бессмыслицу Fluent API, самое чистое решение, которое я мог придумать, оказалось очень простым и не требует ни того, ни другого. Для этого требуется только добавить «частный» конструктор к родительскому классу (или «защищенный», если вы используете отложенную загрузку), в который внедряется ваш объект «DbContext». Просто настройте классы «Родительский» и «Дочерний» как обычные отношения «один ко многим», и теперь, когда контекст вашей базы данных доступен в сущности «Родитель», вы можете сделать так, чтобы «PrimaryChild» и «SecondaryChild» просто возвращали запрос из базы данных с помощью метода Find (). Метод Find () также использует кеширование, поэтому, если вы вызовете метод получения более одного раза, он совершит только одно обращение к базе данных.

Вот документация по этой возможности: https://docs.microsoft.com/en-us/ef/core/modeling/constructors#injecting-services

Примечание. Свойства PrimaryChild и SecondaryChild доступны только для чтения. Чтобы изменить их, установите свойства PrimaryChildId и SecondaryChildId.

class Parent
{
    public Parent() { }
    private MyDbContext Context { get; set; }
    // make the following constructor 'protected' if you're using Lazy Loading
    private Parent(MyDbContext Context) { this.Context = Context; }

    [Key]
    public int ParentId { get; set; }
    public int PrimaryChildId { get; set; }
    public Child PrimaryChild { get { return Context.Children.Find(PrimaryChildId); } }
    public int? SecondaryChildId { get; set; }
    public Child SecondaryChild { get { return Context.Children.Find(SecondaryChildId); } }
    // remaining properties
}

class Child
{
    [Key]
    public int ChildId { get; set; }
    public int ParentId { get; set; }
    public Parent Parent { get; set; }
    // remaining child properties 
}
person user3163495    schedule 15.02.2021
comment
С тех пор мои требования изменились, и у меня даже нет этой настройки, но это довольно приятное решение. Принял ответ, потому что это определенно сработало бы для меня. - person Jesse; 17.02.2021