web-dev-qa-db-fra.com

Comment définir des relations facultatives de clé étrangère dans FluentAPI/Data Annotations avec Entity Framework?

J'ai un (exemple) application avec le code suivant:

public class Posts
{

    [Key]
    [Required]
    public int ID { get; set; }

    [Required]
    public string TypeOfPost { get; set; }

    public int PollID { get; set; }
    public virtual Poll Poll { get; set; }

    public int PostID { get; set; }
    public virtual Post Post { get; set; }

}

En gros, je ne sais pas s’il existe un meilleur moyen de le faire, mais j’ai une liste de publications et les gens peuvent choisir s’il s’agit d’un Poll ou d'un Post, car Entity Framework ne fonctionne pas avec Enums Je le stocke simplement sous forme de chaîne dans TypeOfPost, puis dans l'application, j'interroge par programme Poll ou Post en fonction de la valeur de TypeOfPost.

Je ne pense pas qu'il soit en tout cas possible de définir "Un seul requis" ou similaire, je gère donc toutes les vérifications et les éléments de l'application. (Si quelqu'un connaît un meilleur moyen, dites-le!).

Quoi qu'il en soit, le problème est que je peux très bien fonctionner en accédant à SQL Management Studio et en modifiant manuellement le schéma pour autoriser les valeurs NULL. Toutefois, je ne peux tout simplement pas savoir comment procéder dans FluentAPI et j'ai besoin d'aide.

J'ai essayé les deux choses suivantes:

modelBuilder.Entity<Post>()
    .HasOptional(x => x.Poll).WithOptionalDependent();

modelBuilder.Entity<Post>()
    .HasOptional(x => x.Poll).WithOptionalPrincipal();

Le premier semble créer une colonne supplémentaire dans la base de données qui autorise les valeurs NULL, et le second ne semble rien faire.

Je crois que le premier est celui dont j'ai besoin, mais je dois l'utiliser en combinaison avec [ForeignKey] dans la classe Post. Si j'ai raison, la [ForeignKey] doit-elle figurer sur la propriété virtuelle ou sur l'ID de la propriété?

En outre, quelle est la différence réelle entre WithOptionalDependent et WithOptionalPrincipal? - J'ai lu sur MSDN, mais je ne comprends vraiment pas la différence.

20
Wil

J'essaierais probablement de créer les deux relations un-à-un comme facultatif: obligatoire car un Pollmust a une référence à Posts et un Post également doit référence à Posts:

modelBuilder.Entity<Posts>()
    .HasOptional(x => x.Post)
    .WithRequired();

modelBuilder.Entity<Posts>()
    .HasOptional(x => x.Poll)
    .WithRequired();

Cela fait automatiquement Posts le principal de la relation et Post ou Poll la personne à charge. Le principal a la clé primaire dans la relation, la personne dépendante la clé étrangère qui est également la clé primaire en même temps dans la table PostPoll car il s'agit d'une relation un-à-un. Seulement dans une relation un-à-plusieurs, vous auriez une colonne séparée pour la clé étrangère. Pour une relation un à un, vous devez également supprimer les colonnes de clé étrangère PostId et PollId car Posts fait référence à Post et Poll par sa clé primaire.

Une approche alternative qui semble appropriée dans votre modèle est la cartographie d'héritage. Ensuite, le modèle ressemblerait à ceci:

public abstract class BasePost  // your former Posts class
{
    public int ID { get; set; }
    public string UserName { get; set; }
}

public class Post : BasePost
{
    public string Text { get; set; }
    // other properties of the Post class
}

public class Poll : BasePost
{
    // properties of the Poll class
}

Vous n'avez plus besoin de TypeOfPost car vous pouvez filtrer les deux types concrets à l'aide de l'opérateur OfType LINQ, par exemple:

var x = context.BasePosts.OfType<Post>()
    .Where(p => p.UserName == "Jim")
    .ToList();

Cela sélectionnerait toutes les publications d'un utilisateur particulier, mais pas les sondages.

Vous devez ensuite choisir le type de mappage d'héritage que vous souhaitez utiliser - TPH, TPT ou TPC .

_/ Modifier

Pour obtenir une relation un à plusieurs, vous pouvez spécifier le mappage suivant dans l'API Fluent:

modelBuilder.Entity<Posts>()
    .HasOptional(x => x.Post)
    .WithMany()
    .HasForeignKey(x => x.PostID);

modelBuilder.Entity<Posts>()
    .HasOptional(x => x.Poll)
    .WithMany()
    .HasForeignKey(x => x.PollID);

Les propriétés de la clé étrangère doivent être nullables (int?) pour cela, comme vous l'avez déjà trouvé. La dénomination de vos propriétés de clé étrangère étant conforme à la convention de dénomination utilisée par EF pour le mappage, vous pouvez omettre totalement le mappage Fluent. Cela ne serait requis que si vous aviez des noms non conventionnels (comme PostFK ou quelque chose). Vous pouvez également utiliser des annotations de données (attribut [ForeignKey(...)]) à la place de l'API Fluent.

11
Slauma

La raison pour laquelle il n'autorisait pas les valeurs NULL pour les raisons suivantes:

public int PollID { get; set; }
public virtual Poll Poll { get; set; }

public int PostID { get; set; }
public virtual Post Post { get; set; }

aurait du être

public int? PollID { get; set; }
public virtual Poll Poll { get; set; }

public int? PostID { get; set; }
public virtual Post Post { get; set; }
18
Wil

La clé ForeignKey doit simplement être Nullable pour la rendre facultative - le virtuel est séparé et requis uniquement pour le chargement différé.

Une relation requise dans le code EF déclaratif en premier:

public User User { get; set; }
[ForeignKey("User")]
public int UserId { get; set; }

Une relation optionnelle dans le code EF déclaratif

public User User { get; set; }
[ForeignKey("User")]
public int? UserId { get; set; }

Vous le verrez lorsque vous exécuterez update-database -verbose -f:

ALTER TABLE [dbo].[MyTable] ALTER COLUMN [UserId] [int] NULL
8
Chris Moschini

Quelque chose d'autre qui pourrait aider. La définition d'attributs de clé étrangère (annotations) avec l'attribut [Obligatoire] appliquera également les propriétés de navigation requises par EF, MÊME QUAND, lorsque la propriété FK est nullable. J'ai un cas particulier avec des données existantes dans lequel une propriété FK est requise mais peut ou non référencer un enregistrement dans la relation. Cela a du sens, mais je ne pensais pas qu'EF était aussi «intelligent». 

    [Required] <-- even if the FK is nullable, OrgUnit will be Required
    [StringLength(10)]
    [ForeignKey("OrgUnit"), Column(Order = 1)]
    public string OrgCode
    {
        get;
        set;
    }

    ... other FK, Column order 0   

    public virtual OrgUnit OrgUnit
    {
        get;
        set;
    }
0
Michael K