web-dev-qa-db-fra.com

Relation facultative un à un à l'aide de l'API Entity Framework Fluent

Nous voulons utiliser une relation optionnelle un à un en utilisant Entity Framework Code First. Nous avons deux entités.

public class PIIUser
{
    public int Id { get; set; }

    public int? LoyaltyUserDetailId { get; set; }
    public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}

public class LoyaltyUserDetail
{
    public int Id { get; set; }
    public double? AvailablePoints { get; set; }

    public int PIIUserId { get; set; }
    public PIIUser PIIUser { get; set; }
}

PIIUser peut avoir un LoyaltyUserDetail mais LoyaltyUserDetail doit avoir un PIIUser..__ Nous avons essayé ces techniques d’approche fluide.

modelBuilder.Entity<PIIUser>()
            .HasOptional(t => t.LoyaltyUserDetail)
            .WithOptionalPrincipal(t => t.PIIUser)
            .WillCascadeOnDelete(true);

Cette approche n'a pas créé la clé étrangère LoyaltyUserDetailId dans la table PIIUsers.

Après cela, nous avons essayé le code suivant.

modelBuilder.Entity<LoyaltyUserDetail>()
            .HasRequired(t => t.PIIUser)
            .WithRequiredDependent(t => t.LoyaltyUserDetail);

Mais cette fois, EF n'a créé aucune clé étrangère dans ces 2 tables.

Avez-vous des idées pour résoudre ce problème? Comment pouvez-vous créer une relation un à un en utilisant l’API Fluent Framework?

65
İlkay İlknur

EF Code First prend en charge les relations 1:1 et 1:0..1. Ce dernier est ce que vous recherchez ("un à zéro ou un").

Vos tentatives de fluent sont en disant requis aux deux extrémités dans un cas et facultatif aux deux extrémités dans l'autre.

Ce dont vous avez besoin est optional à une extrémité et required à l’autre.

Voici un exemple du livre Programming E.F. Code First 

modelBuilder.Entity<PersonPhoto>()
.HasRequired(p => p.PhotoOf)
.WithOptional(p => p.Photo);

L'entité PersonPhoto a une propriété de navigation appelée PhotoOf qui pointe vers un type Person. Le type Person a une propriété de navigation appelée Photo qui pointe vers le type PersonPhoto.

Dans les deux classes liées, vous utilisez la clé primaire de chaque type, et non la clé étrangère. c'est-à-dire que vous n'utiliserez pas les propriétés LoyaltyUserDetailId ou PIIUserId. Au lieu de cela, la relation dépend des champs Id des deux types. 

Si vous utilisez l'API fluide comme ci-dessus, vous n'avez pas besoin de spécifier LoyaltyUser.Id en tant que clé étrangère, EF le trouvera. 

Donc, sans avoir votre code pour me tester (je déteste faire ça de ma tête) ... je traduirais cela en votre code

public class PIIUser
{
    public int Id { get; set; }    
    public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}

public class LoyaltyUserDetail
{
    public int Id { get; set; }
    public double? AvailablePoints { get; set; }    
    public PIIUser PIIUser { get; set; }
}

protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
  modelBuilder.Entity<LoyaltyUserDetail>()
  .HasRequired(lu => lu.PIIUser )
  .WithOptional(pi => pi.LoyaltyUserDetail );
}

Cela signifie que la propriété LoyaltyUserDetails PIIUser est required et que la propriété LoyaltyUserDetail de PIIUser est facultative.

Vous pouvez commencer par l'autre extrémité:

modelBuilder.Entity<PIIUser>()
.HasOptional(pi => pi.LoyaltyUserDetail)
.WithRequired(lu => lu.PIIUser);

qui indique maintenant que la propriété LoyaltyUserDetail de PIIUser est facultative et que la propriété PIIUser de LoyaltyUser est requise.

Vous devez toujours utiliser le modèle HAS/WITH.

Les relations HTH et FWIW, un à un (ou un à zéro/un) sont l’une des relations les plus déroutantes à configurer en code afin que vous ne soyez pas seul! :)

91
Julie Lerman

Faites comme si vous aviez une relation un-à-plusieurs entre LoyaltyUserDetail et PIIUser, de sorte que votre mapping devrait être

modelBuilder.Entity<LoyaltyUserDetail>()
       .HasRequired(m => m.PIIUser )
       .WithMany()
       .HasForeignKey(c => c.LoyaltyUserDetailId);

EF doit créer toutes les clés étrangères dont vous avez besoin et juste ne vous souciez pas de WithMany !

22
jr from Yaoundé

Il y a plusieurs problèmes avec votre code. 

Une relation 1: 1 est soit: PK <-PK, où un côté de la clé est également un FK, soit PK <-FK + UC, où le Le côté FK est un non-PK et a un UC. Votre code montre que vous avez FK <-FK, car vous définissez les deux côtés comme ayant un FK, mais c'est faux. Je reconnais que PIIUser est le côté PK et LoyaltyUserDetail est le côté FK. Cela signifie que PIIUser ne possède pas de champ FK, mais LoyaltyUserDetail en a. 

Si la relation 1: 1 est facultative, le côté FK doit avoir au moins un champ nullable. 

p.s.w.g. ci-dessus a répondu à votre question mais a commis une erreur en précisant qu'il/elle définissait également un FK dans PIIUser, ce qui est bien sûr faux comme je l'ai décrit ci-dessus. Définissez donc le champ FK nullable dans LoyaltyUserDetail, définissez l'attribut dans LoyaltyUserDetail pour le marquer comme champ FK, mais ne spécifiez pas de champ FK dans PIIUser.

Vous obtenez l'exception que vous décrivez ci-dessous au post de p.s.w.g, car aucun côté n'est le côté PK (principe de la fin). 

EF n'est pas très bon à 1: 1 car il ne peut pas gérer des contraintes uniques. Je ne suis pas un expert en code d'abord, donc je ne sais pas s'il est capable de créer un UC ou non. 

(edit) btw: A 1: 1 B (FK) signifie qu'il n'y a qu'une seule contrainte FK créée, la cible de B pointant sur la PK de A, et non 2.

3
Frans Bouma
public class User
{
    public int Id { get; set; }
    public int? LoyaltyUserId { get; set; }
    public virtual LoyaltyUser LoyaltyUser { get; set; }
}

public class LoyaltyUser
{
    public int Id { get; set; }
    public virtual User MainUser { get; set; }
}

        modelBuilder.Entity<User>()
            .HasOptional(x => x.LoyaltyUser)
            .WithOptionalDependent(c => c.MainUser)
            .WillCascadeOnDelete(false);

cela résoudra le problème surR&EACUTE;F&EACUTE;RENCEet Touches étrangères  

quandMETTRE &AGRAVE; JOURouSUPPRIMERun enregistrement

2
pampi

Essayez d’ajouter l’attribut ForeignKey à la propriété LoyaltyUserDetail:

public class PIIUser
{
    ...
    public int? LoyaltyUserDetailId { get; set; }
    [ForeignKey("LoyaltyUserDetailId")]
    public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
    ...
}

Et la propriété PIIUser:

public class LoyaltyUserDetail
{
    ...
    public int PIIUserId { get; set; }
    [ForeignKey("PIIUserId")]
    public PIIUser PIIUser { get; set; }
    ...
}
2
p.s.w.g

La seule chose qui confond avec les solutions ci-dessus est que la clé primaire est définie comme " Id" dans les deux tables et si vous avez une clé primaire basée sur le nom de la table, cela ne fonctionnerait pas, j'ai modifié les classes pour illustrer la même chose, c'est-à-dire que la table optionnelle ne devrait pas définir sa propre clé primaire mais devrait utiliser le même nom de clé de la table principale.

public class PIIUser
{
    // For illustration purpose I have named the PK as PIIUserId instead of Id
    // public int Id { get; set; }
    public int PIIUserId { get; set; }

    public int? LoyaltyUserDetailId { get; set; }
    public LoyaltyUserDetail LoyaltyUserDetail { get; set; }
}

public class LoyaltyUserDetail
{
    // Note: You cannot define a new Primary key separately as it would create one to many relationship
    // public int LoyaltyUserDetailId { get; set; }

    // Instead you would reuse the PIIUserId from the primary table, and you can mark this as Primary Key as well as foreign key to PIIUser table
    public int PIIUserId { get; set; } 
    public double? AvailablePoints { get; set; }

    public int PIIUserId { get; set; }
    public PIIUser PIIUser { get; set; }
}

Et puis suivi de 

modelBuilder.Entity<PIIUser>()
.HasOptional(pi => pi.LoyaltyUserDetail)
.WithRequired(lu => lu.PIIUser);

Cela ferait l'affaire, la solution acceptée ne permet pas de l'expliquer clairement, et cela m'a jeté au bout de quelques heures pour trouver la cause.

0
skjagini