EF Core 3.1.7 Anotaciones de datos para múltiples relaciones 1:1 en la tabla
Tengo problemas para descifrar las anotaciones de datos para asignar más de una relación 1:1 para que EF Core 3.11.7 lo entienda y pueda generar una migración.
Tengo una tabla de Persona y una tabla de Notas. Hay una relación de Notas 0:M en Persona. Un registro de persona puede tener 0 o más notas.
En la tabla de notas hay un campo Creado por que es una Persona. También tiene un campo LastEditedBy que también es una persona. EF sigue bombardeando sobre cómo construir la relación para Note.CreatedBy. Si esto no fuera EF, ambos campos serían enteros con el PersonID del registro de persona adecuado. ¿Cómo hacerlo, preferiblemente con anotaciones de datos, explicar esto a EF Core?
Cuando intento crear una migración, falla y dice: System.InvalidOperationException: no se puede determinar la relación representada por la propiedad de navegación 'Note.CreatedBy' de tipo 'Person'. Configure manualmente la relación o ignore esta propiedad usando el atributo '[NotMapped]' o usando 'EntityTypeBuilder.Ignore' en '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; }
}
}
Respuestas
La pregunta es (y esto es lo que hace imposible que EF determine automáticamente las relaciones) ¿cuál es la relación entre Person.Notes
y Note.CreatedBy
/ Note.LastEditBy
- probablemente ninguna? Ha dicho que hay una relación 0:M entre Person
y Note
, pero tenga en cuenta que potencialmente hay 3 relaciones de uno a muchos allí: notas asociadas con la persona, notas creadas por la persona y notas editadas por la persona, lo que potencialmente conduce a 3 FK para Persona en Nota.
También tenga en cuenta que no se requiere ninguna de las propiedades de navegación, pero cuando están presentes deben emparejarse.
Suponiendo que desea 3 relaciones, es decir, no hay relación entre Note.CreatedBy
/ Note.LastEditBy
y Person.Notes
, debe decirle a EF que Note.CreatedBy
no Note.LastEditBy
tiene la propiedad de navegación correspondiente (también conocida como inversa) en Person
. Esto no es posible con las anotaciones de datos. La única anotación de datos disponible para ese propósito [InverseProperty(...)]
no acepta un nombre de cadena vacío/nulo, por lo tanto, no se puede usar para lo que se necesita aquí.
También hay otro problema aquí que encontrará después de resolver el actual, que tampoco se puede resolver con anotaciones de datos. Dado que tiene varias relaciones requeridas (por lo tanto, la eliminación en cascada de forma predeterminada) desde Person
a Note
, crea el famoso problema de "ciclos o múltiples rutas en cascada" con SqlServer, y requiere desactivar al menos una de las eliminaciones en cascada.
Dicho esto, el modelo en cuestión necesita la siguiente configuración fluida mínima:
modelBuilder.Entity<Note>()
.HasOne(e => e.CreatedBy)
.WithMany()
.OnDelete(DeleteBehavior.Restrict);
modelBuilder.Entity<Note>()
.HasOne(e => e.LastEditBy)
.WithMany()
.OnDelete(DeleteBehavior.Restrict);
Los imprescindibles para la emisión original son los pares HasOne
/ . WithMany
Una vez que lo haga, EF Core asignará automáticamente la Person.Notes
colección no asignada a una tercera relación opcional sin propiedad de navegación inversa y propiedad FP oculta (y columna) llamada "PersonId", es decir, el equivalente de
modelBuilder.Entity<Person>()
.HasMany(e => e.Notes)
.WithOne()
.HasForeignKey("PersonId");
Con respecto al segundo problema con múltiples rutas en cascada, en lugar de Restrict
usar cualquier opción que no sea en cascada o la más nueva ClientCascade
. Y podría ser solo para una de las relaciones, tan pronto como rompa el "camino en cascada" (aparentemente no puede romper el ciclo porque lo exige el modelo).