web-dev-qa-db-fra.com

Comment définir la relation plusieurs-à-plusieurs via Fluent API Entity Framework?

Voici mon modèle:

public class TMUrl
{
    //many other properties

    //only property with type Keyword
    public List<Keyword> Keywords{get;set;} 
}

public class Keyword
{
   //many other properties

   //only property with type TMUrl
   public List<TMUrl> Urls{get;set;}
}

Il est donc clair que les deux entités ont une relation plusieurs à plusieurs. J'ai choisi l'api couramment pour parler à l'entité-cadre de cette relation, c'est-à-dire.

modelBuilder.Entity<TMUrl>
               .HasMany(s => s.Keywords)
               .WithMany(s => s.URLs).Map(s =>
                {
                    s.MapLeftKey("KeywordId");
                    s.MapRightKey("UrlId");
                    s.ToTable("KeywordUrlMapping");
                });

mais quand je fais

url.Keywords.Add(dbKey); //where url is object of TMUrl, 
                         //dbKey is an existing/new object of Keyword
db.SaveChanges();

Je reçois une exception

An error occurred while saving entities that do not expose foreign key 
properties for their relationships....

InnerException:

The INSERT statement conflicted with the FOREIGN KEY constraint   
"KeywordMaster_Keyword". The conflict occurred in database "DbName", 
table "dbo.KeywordMaster", column 'Id'.The statement has been terminated.

mais quand j'ajoute également la configuration de l'autre côté, tout fonctionne bien. c'est à dire.

modelBuilder.Entity<KeyWord>
         .HasMany(s => s.URLs)
         .WithMany(s => s.Keywords)
         .Map(s =>
               {
                  s.MapLeftKey("KeywordId");
                  s.MapRightKey("UrlId");
                  s.ToTable("KeywordUrlMapping");
               });

Pourquoi?. Pourquoi je dois ajouter la configuration des deux entités, où j'ai lu ici et de nombreux autres endroits, la configuration pour l'une des entités devrait faire.

Quel est le cas, quand je devrais ajouter une configuration pour les deux entités impliquées dans la relation?

J'ai besoin de comprendre ça. Pourquoi. Veuillez aider.

32
Manish Mishra

Les termes Left et Right dans MapLeftKey et MapRightKey dans le mappage plusieurs-à-plusieurs avec l'API Fluent peuvent être mal compris et je suppose que votre problème est causé par ce malentendu.

On pourrait penser que cela signifie qu'ils décrivent les colonnes qui sont "gauche" et "droite" dans la table de jointure plusieurs-à-plusieurs. C'est en fait le cas si vous laissez EF Code-First créer la base de données et la table de jointure en fonction de votre mappage Fluent.

Mais ce n'est pas nécessairement le cas lorsque vous créez un mappage vers une base de données existante.

Pour illustrer cela avec l'exemple prototypique plusieurs à plusieurs d'un modèle User - Role, supposez que vous avez une base de données existante avec un Users, Roles et Table RoleUsers:

Many-to-many database tables

Maintenant, vous voulez mapper ce schéma de table à un modèle simple:

public class User
{
    public User()
    {
        Roles = new List<Role>();
    }

    public int UserId { get; set; }
    public string UserName { get; set; }
    public ICollection<Role> Roles { get; set; }
}

public class Role
{
    public int RoleId { get; set; }
    public string RoleName { get; set; }
}

Et vous ajoutez le mappage Fluent pour l'entité Users (vous devez le faire de cette façon, car par convention le modèle ci-dessus serait un-à-plusieurs et vous ne pouvez pas commencer à partir de Role côté entité car il n'a pas de collection Users):

modelBuilder.Entity<User>()
    .HasMany(u => u.Roles)
    .WithMany()
    .Map(m =>
    {
        m.MapLeftKey("RoleId");  // because it is the "left" column, isn't it?
        m.MapRightKey("UserId"); // because it is the "right" column, isn't it?
        m.ToTable("RoleUsers");
    });

Ce mappage est incorrect et si vous essayez de mettre "Anna" dans le rôle "Marketing" ...

var anna = ctx.Users.Find(1);
var marketing = ctx.Roles.Find(2);

anna.Roles.Add(marketing);

ctx.SaveChanges();

... SaveChanges lèvera exactement l'exception que vous rencontrez. La raison devient claire lorsque vous capturez la commande SQL envoyée avec SaveChanges:

exec sp_executesql N'insert [dbo].[RoleUsers]([RoleId], [UserId])
values (@0, @1)
',N'@0 int,@1 int',@0=1,@1=2

Donc, EF veut insérer ici une ligne dans la table de jointure RoleUsers avec un RoleId de 1 et un UserId de 2 qui provoque la violation de la contrainte de clé étrangère car il n'y a aucun utilisateur avec UserId2 dans la table Users.

En d'autres termes, le mappage ci-dessus a configuré la colonne RoleId comme clé étrangère vers la table Users et la colonne UserId comme clé étrangère vers la table Roles . Pour corriger le mappage, nous devons utiliser le nom de la colonne "gauche" dans la table de jointure dans MapRightKey et la colonne "droite" dans MapLeftKey:

        m.MapLeftKey("UserId");
        m.MapRightKey("RoleId");

En regardant réellement Intellisense, la description montre clairement ce que "gauche" et "droite" signifient vraiment:

MapLeftKey

Configure le nom des colonnes pour la clé étrangère gauche. La clé étrangère gauche représente la propriété de navigation spécifiée dans l'appel HasMany.

MapRightKey

Configure le nom de la ou des colonnes pour la clé étrangère droite. La clé étrangère droite représente la propriété de navigation spécifiée dans l'appel WithMany.

Ainsi, "Gauche" et "Droite" se réfèrent à l'ordre dans lequel les entités apparaissent dans le mappage Fluent, pas à l'ordre des colonnes dans la table de jointure. L'ordre dans le tableau n'a pas d'importance, vous pouvez le changer sans casser quoi que ce soit car le INSERT envoyé par EF est un "étendu" INSERT qui contient également les noms des colonnes et pas seulement le valeurs.

Peut-être que MapFirstEntityKey et MapSecondEntityKey auraient été un choix moins trompeur de ces noms de méthode - ou peut-être MapSourceEntityKey et MapTargetEntityKey.

C'était un long post de deux mots.

Si je suppose que cela a quelque chose à voir avec votre problème, je dirais que votre premier mappage est incorrect et que vous n'avez besoin que du second mappage correct.

113
Slauma