c# - mvc - Código EF: primera relación uno a uno: la multiplicidad no es válida en el rol*en la relación
entity framework database first español (3)
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 intento migrar mi base de datos aparece el siguiente error:
Class1_Class2_Target:: La multiplicidad no es válida en el rol ''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.
Todavía puede tener
muchos
objetos
Class2
que se refieren al mismo objeto
Class1
.
Además, su modelo no garantiza que un objeto
Class1
haga referencia a una
Class2
referencia a una
Class1
.
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 primaria en la tabla dependiente también es una clave externa para la principal:
(Aquí la
Class1
es la principal)
Ahora en una base de datos relacional, esto todavía no garantiza una asociación 1: 1 (es por eso que dije ''tipo de'').
Es una asociación
1: 0..1
.
Puede haber una
Class1
sin una
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 sincrónicamente.
1: 0..1 es lo más cercano que tenemos.
Mapeo fluido
Para modelar esta asociación en EF, puede usar la API fluida. Aquí está la forma estándar de hacerlo:
class Class1Map : EntityTypeConfiguration<Class1>
{
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 de clave externa alternativas en el modelo, porque la única FK involucrada tiene que ser la clave principal del dependiente.
Lo extraño de este modelo es que EF no te impide crear (y guardar) un objeto de
class2
sin
una
class2
.
Creo que EF debería ser capaz de validar este requisito antes de guardar los cambios, pero, aparentemente, no lo hace.
Del mismo modo, hay formas de eliminar un objeto de
class2
sin eliminar su padre
class1
.
Por lo tanto, este par
HasRequired
-
WithRequired
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 juegan un papel en muchas API, lo que puede ser una maldición, porque cada API elige su propio subconjunto para implementar, 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, si intenta guardar un objeto de
class2
sin una
class2
, obtendrá un error de validación.
Tuve exactamente el mismo problema. Lo que quería es que el esquema de DB tuviera 2 tablas que se crucen entre sí con [clave externa] -> [clave primaria]. Finalmente encontré el camino: Digamos que tenemos 2 clases: Libros y Autores. La clase Libro debería tener una clave externa para el autor que lo creó y la clase Autor debería tener una clave externa para el último libro que escribió. La manera de hacer que EF entienda esto usando el código primero es: (Tenga en cuenta que esto se hace usando una mezcla de anotaciones de datos y 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<Book> books { get; set; }
}
// using fluent API
class BookConfiguration : EntityTypeConfiguration<Book> {
public BookConfiguration() {
this.HasRequired(b => b.author)
.WithMany(a => a.books);
}
}
Esto funciona y crea el esquema de base de datos 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 [Obligatoria]. Si Class2 depende de Class1, especifique [Obligatorio, ForeignKey ("Class1")]. También puede usar una API fluida para configurar esto en su clase de contexto también.