Codice EF: prima relazione One-to-one: la molteplicità non è valida nel ruolo * nella relazione

Sto tentando di fare quanto segue:

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;} } 

Tuttavia ogni volta che provo a migrare il mio database ottengo il seguente errore:

Class1_Class2_Target:: Multiplicity non è valido nel ruolo ‘Class2_Class1_Target’ nella relazione ‘Class2_Class1’. Poiché le proprietà del ruolo dipendente non sono le proprietà chiave, il limite superiore della molteplicità del ruolo dipendente deve essere ‘*’.

Quale potrebbe essere il problema qui?

Il tuo modello non è un’associazione 1: 1. Puoi ancora avere molti oggetti Class2 che si riferiscono allo stesso object Class1 . Inoltre, il tuo modello non garantisce che una Class2 fa riferimento a una Class1 venga rinviata anche da questo object Class1 – La Class1 può riferirsi a qualsiasi object Class2 .

Come configurare 1: 1?

Il modo comune per garantire (una sorta) un’associazione 1: 1 in SQL è avere una tabella per l’ quadro principale e una per l’entity framework dipendente dove la chiave primaria nella tabella dipendente è anche una chiave esterna per l’entity framework:

1: 1

(Qui Class1 è il principale)

Ora in un database relazionale, questo non garantisce ancora un’associazione 1: 1 (ecco perché ho detto “sorta di”). È un’associazione 1: 0..1 . Ci può essere un Class1 senza un Class2 . La verità è che le autentiche associazioni 1: 1 sono impossibili in SQL, perché non esiste un costrutto linguistico che inserisca due righe in tabelle diverse in modo sincrono. 1: 0..1 è il più vicino che otteniamo.

Mappatura fluente

Per modellare questa associazione in EF è ansible utilizzare l’API fluente. Ecco il modo standard per farlo:

 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); } } 

E nel contesto:

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

E questo è rimasto delle tue classi:

 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;} } 

Non è ansible configurare le proprietà di chiavi esterne alternative nel modello, poiché l’unico FK coinvolto deve essere la chiave primaria del dipendente.

La cosa strana di questo modello è che EF non ti impedisce di creare (e salvare) un object class1 senza class2 . Penso che EF dovrebbe essere in grado di convalidare questo requisito prima di salvare le modifiche, ma, a quanto pare, non lo è. Allo stesso modo, ci sono modi per eliminare un object class2 senza eliminare il suo genitore di class1 . Quindi questa coppia HasRequiredWithRequired non è così rigida come sembra (e dovrebbe essere).

Annotazioni di dati

L’unico modo per ottenere questo diritto nel codice è da annotazioni di dati. (Ovviamente il modello di database non sarà ancora in grado di applicare 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;} } 

L’annotazione [Key, ForeignKey("Class1")] dice a EF che Class1 è l’entity framework principale.

Le annotazioni dei dati hanno un ruolo in molte API, che possono essere una maledizione, perché ogni API sceglie il proprio sottoinsieme da implementare, ma qui è utile, perché ora EF non le utilizza solo per progettare il modello di dati, ma anche per convalidare le quadro . Ora se provi a salvare un object class1 senza un class2 otterrai un errore di convalida.

Ho avuto lo stesso identico problema. Quello che volevo è che lo schema DB abbia due tabelle che si incrociano tra loro con [chiave esterna] -> [chiave primaria]. Finalmente ho trovato il modo: diciamo che abbiamo 2 classi: libri e autori. La class Book deve avere una chiave esterna dell’autore che l’ha scritta e la class Author dovrebbe avere una chiave esterna per l’ultimo libro che ha scritto. Il modo in cui EF capisce questo utilizzando prima il codice è: (Si noti che questo viene fatto usando una combinazione di annotazioni di dati e API fluente)

 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); } } 

Questo funziona e crea lo schema DB esatto che volevo. In SQL creerebbe tabelle e chiavi esterne corrispondenti al seguente codice:

 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 delle due classi deve essere creata prima dell’altra e quindi richiede l’annotazione [Richiesto]. Se Class2 dipende da Class1, specificare [Required, ForeignKey (“Class1”)]. Puoi anche usare l’API fluente per configurarlo anche nella tua class di contesto.