Código EF: Primera relación uno a uno: la multiplicidad no es válida en Role * en relación

Estoy intentando hacer lo siguiente:

public class class1 { public int Id {get;set;} [ForeignKey("Class2")] public int Class2Id {get;set;} public virtual Class2 Class2 {get;set;} } public class class2 { public int Id { get; set;} [Required] public virtual int Class1Id {get;set;} [Required] [ForeignKey("Class1Id")] public Class1 Class1 {get;set;} } 

Sin embargo, cada vez que bash migrar mi base de datos, aparece el siguiente error:

Class1_Class2_Target:: La multiplicidad no es válida en la función ‘Class2_Class1_Target’ en la relación ‘Class2_Class1’. Debido a que las propiedades del rol dependiente no son las propiedades clave, el límite superior de la multiplicidad del rol dependiente debe ser ‘*’.

¿Cuál podría ser el problema aquí?

Su modelo no es una asociación 1: 1. Aún puede tener muchos objetos Class2 que se refieren al mismo objeto Class1 . Además, su modelo no garantiza que un objeto Class2 refiera una Class1 se refiere a una Class1 : la Class1 puede referirse a cualquier objeto Class2 .

Cómo configurar 1: 1?

La forma común de garantizar (más o menos) una asociación 1: 1 en SQL es tener una tabla para la entidad principal y otra para la entidad dependiente donde la clave principal en la tabla dependiente también sea una clave externa para el principal:

1: 1

(Aquí Class1 es el principal)

Ahora en una base de datos relacional, esto todavía no garantiza una asociación 1: 1 (por eso dije ‘algo así’). Es una asociación 1: 0..1 . Puede haber un Class1 sin un Class2 . La verdad es que las asociaciones genuinas 1: 1 son imposibles en SQL, porque no hay una construcción de lenguaje que inserte dos filas en diferentes tablas de forma sincrónica. 1: 0..1 es lo más cercano que tenemos.

Mapeo fluido

Para modelar esta asociación en EF, puede usar la API fluida. Esta es la manera estándar de hacerlo:

 class Class1Map : EntityTypeConfiguration { public Class1Map() { this.HasKey(c => c.Id); this.Property(c => c.Id) .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); this.HasRequired(c1 => c1.Class2).WithRequiredPrincipal(c2 => c2.Class1); } } 

Y en el contexto:

 protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new Class1Map()); } 

Y esto queda de tus clases:

 public class Class1 { public int Id {get;set;} public virtual Class2 Class2 {get;set;} } public class Class2 { public int Id {get;set;} public virtual Class1 Class1 {get;set;} } 

No hay forma de configurar propiedades alternativas de clave externa en el modelo, porque el único FK involucrado debe ser la clave primaria del dependiente.

Lo extraño de este modelo es que EF no le impide crear (y guardar) un objeto de class2 sin class2 . Creo que EF debería ser capaz de validar este requisito antes de guardar los cambios, pero aparentemente no es así. Del mismo modo, hay formas de eliminar un objeto de class2 sin eliminar su padre de class1 . Entonces este par HasRequiredWithRequired no es tan estricto como parece (y debería ser).

Anotaciones de datos

La única forma de hacerlo bien en el código es mediante anotaciones de datos. (Por supuesto, el modelo de base de datos aún no podrá aplicar 1: 1)

 public class Class1 { public int Id {get;set;} [Required] public virtual Class2 Class2 {get;set;} } public class Class2 { [Key, ForeignKey("Class1")] public int Id {get;set;} [Required] public virtual Class1 Class1 {get;set;} } 

La anotación [Key, ForeignKey("Class1")] le dice a EF que Class1 es la entidad principal.

Las anotaciones de datos desempeñan un papel en muchas API, lo que puede ser una maldición, porque cada API elige su propio subconjunto para implementarlo, pero aquí resulta útil, porque ahora EF no solo las usa para diseñar el modelo de datos, sino también para validar entidades . Ahora bien, si intentas guardar un objeto de class2 sin una class2 , obtendrás un error de validación.

Tuve exactamente el mismo problema. Lo que quería es que el esquema DB tenga 2 tablas que se crucen entre sí con [clave externa] -> [clave principal]. Finalmente encontré el camino: digamos que tenemos 2 clases: libros y autores. La clase Libro debe tener una clave externa para el autor que la creó y la clase Autor debe tener una clave externa para el último libro que escribió. La forma de que EF lo entienda primero es: (tenga en cuenta que esto se hace usando una combinación de anotaciones de datos y una API fluida)

 public class Book { ... public Guid BookId ... public Guid AuthorId { get; set; } [ForeignKey("AuthorId")] public virtual Author author { get; set; } } public class Author { ... public Guid AuthorId ... public Guid? LatestBookId { get; set; } [ForeignKey("LatestBookId")] public virtual Book book { get; set; } public virtual ICollection books { get; set; } } // using fluent API class BookConfiguration : EntityTypeConfiguration { public BookConfiguration() { this.HasRequired(b => b.author) .WithMany(a => a.books); } } 

Esto funciona y crea el esquema DB exacto que quería. En SQL crearía tablas y claves foráneas correspondientes al siguiente código:

 CREATE TABLE [dbo].[Book]( [BookId] [uniqueidentifier] NOT NULL, [AuthorId] [uniqueidentifier] NOT NULL, ... CONSTRAINT [PK_dbo.Book] PRIMARY KEY CLUSTERED ( [BookId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] ... GO ALTER TABLE [dbo].[Book] WITH CHECK ADD CONSTRAINT [FK_dbo.Book.Author_AuthorId] FOREIGN KEY([AuthorId]) REFERENCES [dbo].[Author] ([AuthorId]) GO ... CREATE TABLE [dbo].[Author]( [AuthorId] [uniqueidentifier] NOT NULL, [LatestBookId] [uniqueidentifier] NULL, ... CONSTRAINT [PK_dbo.Author] PRIMARY KEY CLUSTERED ( [AuthorId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY] ... GO ALTER TABLE [dbo].[Author] WITH CHECK ADD CONSTRAINT [FK_dbo.Author_dbo.Book_LatestBookId] FOREIGN KEY([LatestBookId]) REFERENCES [dbo].[Book] ([BookId]) GO ... 

Una de las dos clases debe crearse antes que la otra y, por lo tanto, requiere la anotación [Obligatorio]. Si Class2 depende de Class1, especifique [Required, ForeignKey (“Class1”)]. También puede usar una API fluida para configurar esto en su clase de contexto.