EF Core 3.1.7 Аннотации данных для нескольких отношений 1: 1 в таблице

Aug 17 2020

У меня возникают проблемы с определением аннотаций к данным для сопоставления более чем одного отношения 1: 1, чтобы EF Core 3.11.7 понимал это и мог выполнить миграцию.

У меня есть таблица Person и таблица Notes. В Person существует отношение 0: M Notes. Запись о человеке может содержать 0 или более заметок.

В таблице заметок есть поле CreatedBy, которое является человеком. Он также имеет поле LastEditedBy, которое также является человеком. EF продолжает ломать голову над тем, как построить отношения для Note.CreatedBy. Если бы это был не EF, оба поля были бы целыми числами с PersonID соответствующей записи о человеке. Как это, предпочтительно с помощью аннотаций к данным, объяснить это EF Core?

Когда я пытаюсь создать миграцию, она терпит неудачу и сообщает: System.InvalidOperationException: Невозможно определить взаимосвязь, представленную свойством навигации «Note.CreatedBy» типа «Person». Либо настройте связь вручную, либо проигнорируйте это свойство с помощью атрибута «[NotMapped]» или с помощью EntityTypeBuilder.Ignore в «OnModelCreating».

    using System;
    using System.Collections.Generic;
    using System.ComponentModel.DataAnnotations;
    using System.ComponentModel.DataAnnotations.Schema;
    using System.Linq;
    using System.Threading.Tasks;

namespace VetReg.Domain.Model
{
    public class Family
    {
        public int FamilyID { get; set; } = -1;
        public string FamilyName { get; set; }
        public List<Pet> Pets { get; set; } = new List<Pet>();
        public List<PersonFamily> People { get; set; }
        public int AddressID { get; set; } = -1;
        public List<Note> Notes { get; set; }
    }

    public class Person
    {
        public int PersonID { get; set; }
        public string LastName { get; set; }
        public string FirstName { get; set; }
        public DateTime? Birthdate { get; set; }
        public string Password { get; set; }
        public List<PersonFamily> Families { get; set; }
        public List<Note> Notes { get; set; }
    } // class People

    public class Note
    {
        public int NoteID { get; set; }

        public int CreatedByID { get; set; }

        [ForeignKey("CreatedByID")]
        public Person CreatedBy { get; set; }
        public DateTime DateCreated { get; set; }

        public int LastEditByID { get; set; }

        [ForeignKey("LastEditByID")]
        public Person LastEditBy { get; set; }
        public DateTime? LastEditDate { get; set; }
        public string NoteText { get; set; }
    }

    public class PersonFamily
    {
        public int PersonID { get; set; }
        public int FamilyID { get; set; }
        public Person Person { get; set; }
        public Family Family { get; set; }
    }

}

Ответы

IvanStoev Aug 17 2020 at 15:11

Возникает вопрос (и это то, что делает невозможным для EF автоматическое определение отношений), какова связь между Person.Notesи Note.CreatedBy/ Note.LastEditBy- вероятно, нет? Вы сказали, что существует отношение 0: M между Personи Note, но обратите внимание, что потенциально существует 3 отношения один-ко-многим - заметки, связанные с человеком, заметки, созданные человеком, и заметки, редактируемые человеком, что потенциально приводит к 3 FK для Человек в примечании.

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

Предполагая, что вам нужны 3 отношения, то есть нет никакой связи между Note.CreatedBy/ Note.LastEditByи Person.Notes, вам нужно сообщить EF об этом Note.CreatedByи Note.LastEditByне иметь соответствующего (также известного как обратное) свойства навигации в Person. Это невозможно с аннотациями к данным. Единственная доступная аннотация данных для этой цели [InverseProperty(...)]не принимает пустое / нулевое имя строки, поэтому не может использоваться для того, что здесь необходимо.

Также здесь есть еще одна проблема, с которой вы столкнетесь после разрешения текущего, которую также нельзя решить с помощью аннотаций данных. Поскольку у вас есть несколько требуемых (по умолчанию каскадное удаление) отношений от Personдо Note, это создает известную проблему «циклов или нескольких каскадных путей» с SqlServer и требует отключения по крайней мере одного из каскадных удалений.

С учетом сказанного, рассматриваемая модель требует следующей минимальной плавной конфигурации:

modelBuilder.Entity<Note>()
    .HasOne(e => e.CreatedBy)
    .WithMany()
    .OnDelete(DeleteBehavior.Restrict);

modelBuilder.Entity<Note>()
    .HasOne(e => e.LastEditBy)
    .WithMany()
    .OnDelete(DeleteBehavior.Restrict);

Существенными для исходного выпуска являются пары HasOne/ WithMany. Как только вы это сделаете, EF Core автоматически сопоставит несопоставленную Person.Notesколлекцию с третьей необязательной связью без свойства обратной навигации и теневого свойства FP (и столбца) под названием «PersonId», т. Е. Эквивалента

modelBuilder.Entity<Person>()
    .HasMany(e => e.Notes)
    .WithOne()
    .HasForeignKey("PersonId");

Что касается второй проблемы с несколькими каскадными путями, вместо этого Restrictвы можете использовать любую некаскадную опцию или более новую ClientCascade. И это может быть только одно из отношений, как только оно нарушает «каскадный путь» (очевидно, вы не можете разорвать цикл, потому что этого требует модель).