EF Core 3.1.7 Anotações de dados para várias relações 1:1 na tabela

Aug 17 2020

Estou tendo problemas para descobrir as anotações de dados para mapear mais de um relacionamento 1:1 para que o EF Core 3.11.7 entenda e possa criar uma migração.

Eu tenho uma tabela Person e uma tabela Notes. Há um relacionamento 0:M Notes em pessoa. Um registro de pessoa pode ter 0 ou mais notas.

Na tabela de notas há um campo CreatedBy que é uma Pessoa. Ele também tem um campo LastEditedBy que também é uma pessoa. EF continua bombardeando sobre como construir o relacionamento para Note.CreatedBy. Se não fosse EF, ambos os campos seriam inteiros com o PersonID do registro de pessoa adequado. Como isso, de preferência com anotações de dados, explica isso para o EF Core?

Quando tento criar uma migração, ele falha e diz: System.InvalidOperationException: Não é possível determinar o relacionamento representado pela propriedade de navegação 'Note.CreatedBy' do tipo 'Pessoa'. Configure manualmente o relacionamento ou ignore essa propriedade usando o atributo '[NotMapped]' ou usando 'EntityTypeBuilder.Ignore' em '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; }
    }

}

Respostas

IvanStoev Aug 17 2020 at 15:11

A questão é (e é isso que torna impossível para EF determinar automaticamente as relações) qual é a relação entre Person.Notese Note.CreatedBy/ Note.LastEditBy- provavelmente nenhum? Você disse que há uma relação 0:M entre Persone Note, mas observe que há potencialmente 3 relações um-para-muitos - notas associadas à pessoa, notas criadas pela pessoa e notas editadas pela pessoa, o que potencialmente leva a 3 FKs para Pessoa em Nota.

Observe também que nenhuma das propriedades de navegação é necessária, mas, quando presentes, elas devem ser emparelhadas.

Supondo que você queira 3 relacionamentos, ou seja, não há relação entre Note.CreatedBy/ Note.LastEditBye Person.Notes, você precisa informar isso ao EF Note.CreatedBye Note.LastEditBynão possui propriedade de navegação correspondente (também conhecida como inversa) em Person. Isso não é possível com anotações de dados. A única anotação de dados disponível para esse fim [InverseProperty(...)]não aceita nome de string vazio/nulo, portanto não pode ser usado para o que é necessário aqui.

Além disso, há outro problema aqui que você encontrará após resolver o atual, que também não pode ser resolvido com anotações de dados. Como você tem vários relacionamentos necessários (portanto, exclusão em cascata por padrão) de Personpara Note, ele cria o famoso problema de "ciclos ou vários caminhos em cascata" com o SqlServer e requer a desativação de pelo menos uma exclusão em cascata.

Dito isso, o modelo em questão precisa da seguinte configuração fluente mínima:

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

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

O essencial para a emissão original são os pares HasOne/ . WithManyDepois de fazer isso, o EF Core mapeará automaticamente a Person.Notescoleção não mapeada para um terceiro relacionamento opcional sem propriedade de navegação inversa e propriedade FP de sombra (e coluna) chamada "PersonId", ou seja, o equivalente a

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

Com relação ao segundo problema com vários caminhos em cascata, em vez de Restrictvocê pode usar qualquer opção sem cascata ou o mais recente ClientCascade. E pode ser para apenas um dos relacionamentos, desde que quebre o "caminho em cascata" (aparentemente não dá para quebrar o ciclo porque é exigido pelo modelo).