web-dev-qa-db-de.com

Wie definiere ich optionale Fremdschlüsselbeziehungen in FluentAPI/Data Annotations mit dem Entity Framework?

Ich habe eine (Beispiel-) Anwendung mit folgendem Code:

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

}

Grundsätzlich weiß ich nicht, ob es einen besseren Weg gibt, aber ich habe eine Liste von Posts, und die Leute können wählen, ob es sich um eine Poll oder eine Post handelt, da das Entity Framework mit Enums nicht funktioniert. Ich speichere es einfach als Zeichenfolge in TypeOfPost und frage dann in der Anwendung programmgesteuert entweder Poll oder Post basierend auf dem Wert von TypeOfPost ab.

Ich glaube, es gibt sowieso keine Einstellung "Nur eine erforderlich" oder ähnliches, also kümmere ich mich um alle Überprüfungen und Sachen in der Anwendung. (Wenn jemand einen besseren Weg kennt, sag es bitte!).

Das Problem ist jedoch, dass ich dieses Problem beheben kann, indem ich zu SQL Management Studio gehe und das Schema manuell bearbeite, um Nullen zuzulassen. Ich kann jedoch nicht herausfinden, wie dies in der FluentAPI geschehen soll, und benötige Hilfe.

Ich habe beide der folgenden versucht:

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

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

Die erste scheint eine zusätzliche Spalte in der Datenbank zu erstellen, die Nullen zulässt, und die zweite scheint nichts zu tun.

Ich glaube, der erste ist der, den ich brauche, aber ich muss ihn in Kombination mit [ForeignKey] in der Klasse Post verwenden. Wenn ich hier richtig bin, sollte der [ForeignKey] auf die virtuelle Eigenschaft oder die ID der Eigenschaft gehen?

Was ist außerdem der tatsächliche Unterschied zwischen WithOptionalDependent und WithOptionalPrincipal? - Ich habe auf MSDN gelesen, aber ich verstehe den Unterschied wirklich nicht.

20
Wil

Ich würde wahrscheinlich versuchen, die beiden Eins-zu-Eins-Beziehungen als optional: required zu erstellen, da eine Pollmust einen Verweis auf Posts und eine Post auch must hat. einen Verweis auf Posts haben:

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

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

Dies macht Posts automatisch zum Prinzipal in der Beziehung und Post oder Poll zum Abhängigen. Der Principal hat den Primärschlüssel in der Beziehung, der abhängige Fremdschlüssel, der gleichzeitig auch der Primärschlüssel ist, in Post/Poll Tabelle, da es sich um eine Eins-zu-Eins-Beziehung handelt. Nur in einer Eins-zu-Viele-Beziehung hätten Sie eine separate Spalte für den Fremdschlüssel. Für eine Eins-zu-Eins-Beziehung müssen Sie auch die Fremdschlüsselspalten PostId und PollId entfernen, da Posts über ihren Primärschlüssel auf Post und Poll verweist.

Ein alternativer Ansatz, der in Ihrem Modell als angemessen erscheint, ist die Vererbungszuordnung. Dann würde das Modell so aussehen:

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
}

Sie brauchen die Variable TypeOfPost dann nicht mehr, da Sie die beiden konkreten Typen mit dem Operator OfType LINQ filtern können, zum Beispiel:

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

Dies würde alle Beiträge eines bestimmten Benutzers auswählen, nicht jedoch die Umfragen.

Sie müssen dann entscheiden, welche Art von Vererbungszuordnung Sie verwenden möchten - TPH, TPT oder TPC .

Bearbeiten

Um eine Eins-zu-Viele-Beziehung zu erhalten, können Sie die folgende Zuordnung in der Fluent-API angeben:

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

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

Die Fremdschlüsseleigenschaften müssen hierfür nullfähig sein (int?), wie Sie dies bereits gefunden haben. Da die Benennung Ihrer Fremdschlüsseleigenschaften der Namenskonvention entspricht, die EF für das Mapping verwendet, können Sie das Fluent-Mapping ganz weglassen. Dies ist nur erforderlich, wenn Sie unkonventionelle Namen (wie PostFK oder etwas anderes) haben. Sie können dann anstelle der Fluent-API auch Datenanmerkungen ([ForeignKey(...)]-Attribut) verwenden.

11
Slauma

Der Grund, warum es keine Nullen zuließ, weil Folgendes:

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

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

gewesen sein sollte

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

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

Der ForeignKey muss nur nullfähig sein, um ihn optional zu machen - virtuell ist separat und nur für Lazy Loading erforderlich.

Eine erforderliche Beziehung im deklarativen EF-Code First:

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

Eine optionale Beziehung im deklarativen EF-Code First:

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

Sie werden es sehen, wenn Sie update-database -verbose -f ausführen:

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

Noch etwas, das helfen könnte. Durch Festlegen von Fremdschlüsselattributen (Anmerkungen) mit dem Attribut [Required] werden auch die erforderlichen EF-Navigationseigenschaften erzwungen, WENN die FK-Eigenschaft nullfähig ist. Ich habe einen Sonderfall mit veralteten Daten, bei denen eine FK-Eigenschaft erforderlich ist, aber möglicherweise einen Datensatz in der Beziehung referenzieren kann. Das macht Sinn, aber ich glaube nicht, dass EF so 'schlau' ist. 

    [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