web-dev-qa-db-fra.com

Relation bi-univoque EF Code-First: la multiplicité n'est pas valide dans le rôle * dans la relation

J'essaie de faire ce qui suit:

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

Cependant, chaque fois que j'essaie de migrer ma base de données, j'obtiens l'erreur suivante:

Class1_Class2_Target:: la multiplicité n'est pas valide dans le rôle 'Class2_Class1_Target' dans la relation 'Class2_Class1'. Étant donné que les propriétés du rôle dépendant ne sont pas les propriétés clés, la limite supérieure de la multiplicité du rôle dépendant doit être "*".

Quel pourrait être le problème ici?

28
JensOlsen112

Votre modèle n'est pas une association 1: 1. Vous pouvez toujours avoir beaucoupClass2 Objets faisant référence au même unClass1 Objet. De plus, votre modèle ne garantit pas qu'un Class2 Faisant référence à un Class1 Est également renvoyé par cet objet Class1 - Class1 Peut faire référence à n'importe quel Class2 Objet.

Comment configurer 1: 1?

La manière courante de garantir (en quelque sorte) une association 1: 1 en SQL est d'avoir une table pour l'entité principal et une pour l'entité dépendante où la clé primaire dans la table dépendante est également une clé étrangère pour le principal:

1:1

(Ici Class1 Est le principal)

Maintenant, dans une base de données relationnelle, cela ne garantit toujours pas une association 1: 1 (c'est pourquoi j'ai dit "en quelque sorte"). C'est une association 1: 0..1. Il peut y avoir un Class1 Sans un Class2. La vérité est que de véritables associations 1: 1 sont impossibles dans SQL, car il n'y a pas de construction de langage qui insère deux lignes dans des tables différentes de manière synchrone. 1: 0..1 est le plus proche que nous obtenons.

Cartographie fluide

Pour modéliser cette association dans EF, vous pouvez utiliser l'API couramment. Voici la manière standard de procéder:

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

Et dans le contexte:

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

Et cela reste de vos cours:

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

Il n'y a aucun moyen de configurer d'autres propriétés de clé étrangère dans le modèle, car le seul FK impliqué doit l'être la clé primaire de la personne à charge.

Ce qui est étrange avec ce modèle, c'est que EF ne vous empêche pas de créer (et d'enregistrer) un objet class1 sans a class2. Je pense que EF devrait être capable de valider cette exigence avant d'enregistrer les modifications, mais, apparemment, ce n'est pas le cas. De même, il existe des moyens de supprimer un objet class2 Sans supprimer son parent class1. Donc, cette paire HasRequired - WithRequired n'est pas aussi stricte qu'elle en a l'air (et devrait l'être).

Annotations de données

La seule façon de faire les choses correctement dans le code est par des annotations de données. (Bien sûr, le modèle de base de données ne pourra toujours pas appliquer 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'annotation [Key, ForeignKey("Class1")] indique à EF que Class1 Est l'entité principale.

Les annotations de données jouent un rôle dans de nombreuses API, ce qui peut être une malédiction, car chaque API choisit son propre sous-ensemble à implémenter, mais ici, il est utile, car maintenant EF ne les utilise plus uniquement pour concevoir le modèle de données, mais aussi à valider entités. Maintenant, si vous essayez d'enregistrer un objet class1 Sans un class2, Vous obtiendrez une erreur de validation.

58
Gert Arnold

J'ai eu exactement le même problème. Ce que je voulais, c'est que le schéma DB ait 2 tables qui se croisent avec [clé étrangère] -> [clé primaire]. Enfin, j'ai trouvé le chemin: disons que nous avons 2 classes: livres et auteurs. La classe Book doit avoir une clé étrangère pour l'auteur qui l'a écrit et la classe Author doit avoir une clé étrangère pour le dernier livre qu'il a écrit. La façon de faire comprendre à EF d'abord cela en utilisant du code est la suivante: (Notez que cela se fait en utilisant un mélange d'annotations de données et d'une API fluide)

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

}

Cela fonctionne et crée le schéma de base de données exact que je voulais. En SQL, il créerait des tables et des clés étrangères correspondant au code suivant:

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

...
3
Erez Lerner

L'une des deux classes doit être créée avant l'autre et nécessite donc l'annotation [Obligatoire]. Si Class2 dépend de Class1, spécifiez [Obligatoire, ForeignKey ("Class1")]. Vous pouvez également utiliser l'API couramment pour configurer cela dans votre classe de contexte également.

0
RizJa