EF Core 3.1.7 Annotations de données pour plusieurs relations 1:1 dans le tableau

Aug 17 2020

J'ai des problèmes pour comprendre les annotations de données pour mapper plus d'une relation 1:1 afin qu'EF Core 3.11.7 le comprenne et puisse créer une migration.

J'ai une table Person et une table Notes. Il existe une relation 0:M Notes dans Person. Un enregistrement de personne peut avoir 0 ou plusieurs notes.

Dans la table des notes se trouve un champ CreatedBy qui est une personne. Il a également un champ LastEditedBy qui est également une personne. EF continue de bombarder sur la façon de construire la relation pour Note.CreatedBy. Si ce n'était pas EF, les deux champs seraient des entiers avec le PersonID de l'enregistrement de personne approprié. Comment faire, de préférence avec des annotations de données, expliquer cela à EF Core ?

Lorsque j'essaie de créer une migration, elle échoue et indique : System.InvalidOperationException : Impossible de déterminer la relation représentée par la propriété de navigation "Note.CreatedBy" de type "Person". Configurez manuellement la relation ou ignorez cette propriété en utilisant l'attribut '[NotMapped]' ou en utilisant 'EntityTypeBuilder.Ignore' dans '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; }
    }

}

Réponses

IvanStoev Aug 17 2020 at 15:11

La question est (et c'est ce qui rend impossible à EF de déterminer automatiquement les relations) quelle est la relation entre Person.Noteset Note.CreatedBy/ Note.LastEditBy- aucune probablement ? Vous avez dit qu'il existe une relation 0: M entre Personet Note, mais notez qu'il existe potentiellement 3 relations un à plusieurs - des notes associées à une personne, des notes créées par une personne et des notes modifiées par une personne, ce qui conduit potentiellement à 3 FK à Personne en note.

Notez également qu'aucune des propriétés de navigation n'est requise, mais lorsqu'elles sont présentes, elles doivent être associées.

En supposant que vous vouliez 3 relations, c'est-à-dire qu'il n'y a pas de relation entre Note.CreatedBy/ Note.LastEditByet Person.Notes, vous devez le dire à EF Note.CreatedByet Note.LastEditByne pas avoir la propriété de navigation correspondante (alias inverse) dans Person. Ceci n'est pas possible avec les annotations de données. La seule annotation de données disponible à cette fin [InverseProperty(...)]n'accepte pas de nom de chaîne vide/null, et ne peut donc pas être utilisée pour ce qui est nécessaire ici.

Il y a aussi un autre problème ici que vous rencontrerez après avoir résolu le courant, qui ne peut pas non plus être résolu avec des annotations de données. Étant donné que vous avez plusieurs relations requises (donc suppression en cascade par défaut) de Personà Note, cela crée le fameux problème "cycles ou plusieurs chemins en cascade" avec SqlServer, et nécessite de désactiver au moins une des suppressions en cascade.

Cela étant dit, le modèle en question nécessite la configuration fluide minimale suivante :

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

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

L'essentiel pour le numéro d'origine sont les paires HasOne/ . WithManyUne fois que vous faites cela, EF Core mappera automatiquement la Person.Notescollection non mappée à une troisième relation facultative sans propriété de navigation inverse et propriété FP fantôme (et colonne) appelée "PersonId", c'est-à-dire l'équivalent de

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

En ce qui concerne le deuxième problème avec plusieurs chemins en cascade, Restrictvous pouvez utiliser n'importe quelle option non en cascade ou le plus récent ClientCascade. Et cela pourrait être pour une seule des relations, dès qu'elle rompt le "chemin en cascade" (apparemment, vous ne pouvez pas rompre le cycle car il est exigé par le modèle).