Eins zu eins optionale Beziehung mit Entity Framework Fluent API

79

Wir möchten eine optionale Eins-zu-Eins-Beziehung mit Entity Framework Code First verwenden. Wir haben zwei Einheiten.

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

PIIUserkann eine haben LoyaltyUserDetail, LoyaltyUserDetailmuss aber eine haben PIIUser. Wir haben diese fließenden Ansatztechniken ausprobiert.

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

Dieser Ansatz hat keinen LoyaltyUserDetailIdFremdschlüssel in der PIIUsersTabelle erstellt.

Danach haben wir den folgenden Code ausprobiert.

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

Diesmal hat EF jedoch keine Fremdschlüssel in diesen beiden Tabellen erstellt.

Haben Sie Ideen für dieses Problem? Wie können wir eine eins zu eins optionale Beziehung mit der fließenden API des Entity Frameworks erstellen?

İlkay İlknur
quelle

Antworten:

101

EF Code First unterstützt 1:1und 1:0..1Beziehungen. Letzteres ist das, wonach Sie suchen ("Eins zu Null oder Eins").

Ihre Versuche, fließend zu sprechen, sind in einem Fall an beiden Enden erforderlich und in dem anderen an beiden Enden optional .

Was Sie benötigen, ist an einem Ende optional und am anderen erforderlich .

Hier ist ein Beispiel aus dem Buch Programming EF Code First

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

Die PersonPhotoEntität verfügt über eine Navigationseigenschaft PhotoOf, die auf einen PersonTyp verweist . Der PersonTyp verfügt über eine Navigationseigenschaft Photo, die auf den PersonPhotoTyp verweist .

In den beiden verwandten Klassen verwenden Sie den Primärschlüssel jedes Typs und keine Fremdschlüssel . Das heißt, Sie werden die Eigenschaften LoyaltyUserDetailIdoder nicht verwenden PIIUserId. Stattdessen hängt die Beziehung von den IdFeldern beider Typen ab.

Wenn Sie die fließende API wie oben verwenden, müssen Sie sie nicht LoyaltyUser.Idals Fremdschlüssel angeben. EF wird dies herausfinden.

Also ohne Ihren Code, um mich selbst zu testen (ich hasse es, dies von meinem Kopf aus zu tun) ... würde ich dies in Ihren Code übersetzen als

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

Das heißt, die LoyaltyUserDetails- PIIUserEigenschaft ist erforderlich und die PIIUser- LoyaltyUserDetailEigenschaft ist optional.

Sie könnten am anderen Ende beginnen:

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

Dies besagt nun, dass die LoyaltyUserDetailEigenschaft von PIIUser optional ist und die PIIUserEigenschaft von LoyaltyUser erforderlich ist.

Sie müssen immer das Muster HAS / WITH verwenden.

HTH und FWIW, Eins-zu-Eins-Beziehungen (oder Eins-zu-Null / Eins-Beziehungen) sind eine der verwirrendsten Beziehungen, die zuerst im Code konfiguriert werden müssen, damit Sie nicht allein sind! :) :)

Julie Lerman
quelle
1
Schränkt das nicht den Benutzer ein, auf dem das FK-Feld ausgewählt werden soll? (Ich denke, er will das, aber er hat sich nicht gemeldet, also weiß ich es nicht), da er anscheinend ein FK-Feld in der Klasse haben möchte.
Frans Bouma
1
Einverstanden. Es ist eine Einschränkung der Funktionsweise von EF. 1: 1 und 1: 0..1 hängen von den Primärschlüsseln ab. Ansonsten denke ich, dass Sie sich in die "einzigartige FK" verirren, die in EF immer noch nicht unterstützt wird. :( (Sie sind eher ein DB-Experte ... ist das richtig ... hier geht es wirklich um einen einzigartigen FK?) Und nicht in der kommenden Ef6 gemäß: entityframework.codeplex.com/workitem/299
Julie Lerman
1
Ja @FransBouma, wir wollten die Felder PIIUserId und LoyaltUserId als Fremdschlüssel verwenden. Aber EF schränkt uns in dieser Situation ein, wie Sie und Julie erwähnt haben. Danke für die Antworten.
İlkay İlknur
@ JulieLerman warum benutzt du WithOptional? Was ist der Unterschied mit WithRequiredDependentund WithRequiredOptional?
Willy
1
WithOptional verweist auf eine Beziehung, die optional ist, z. B. 0..1 (null oder eins), da im OP "PIIUser hat möglicherweise einen LoyaltyUserDetail" angegeben ist. Und Ihre Verwirrung, die zur zweiten Frage führt, ist, weil Sie einen der Begriffe falsch verstanden haben. ;) Diese sind WithRequiredDependent und WithRequiredPrincipal. Ist das sinnvoller? Zeigen auf ein erforderliches Ende, das entweder das abhängige (alias "Kind") oder das Prinzipal alias "Elternteil" ist. Selbst in einer Eins-zu-Eins-Beziehung, in der beide gleich sind, muss EF eins als Prinzipal und eins als abhängig bezeichnen. HTH!
Julie Lerman
26

Machen Sie es einfach so, wenn Sie eine Eins-zu-Viele-Beziehung haben LoyaltyUserDetailund PIIUserIhre Zuordnung so sein sollte

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

EF sollte alle Fremdschlüssel erstellen, die Sie benötigen, und WithMany einfach nicht interessieren !

jr aus Yaoundé
quelle
3
Die Antwort von Julie Lerman wurde akzeptiert (und sollte meiner Meinung nach akzeptiert bleiben), weil sie die Frage beantwortet und ausführlich erklärt, warum dies mit Hintergrundinformationen und Diskussionen der richtige Weg ist. Antworten zum Kopieren und Einfügen sind sicherlich überall in SO verfügbar, und ich habe selbst einige verwendet, aber als professioneller Entwickler sollten Sie sich mehr darum kümmern, ein besserer Programmierer zu werden und nicht nur den Code zum Erstellen zu bekommen. Das heißt, jr hat es richtig gemacht, wenn auch ein Jahr später.
Mike Devenney
1
@ClickOk Ich weiß, dass dies eine alte Frage und ein alter Kommentar ist, aber dies ist nicht die richtige Antwort, da sie eins zu viele als Problemumgehung verwendet hat. Dies führt nicht zu einer Eins-zu-Eins- oder Eins-zu-Null-Beziehung
Mahmoud Darwish,
3

Es gibt mehrere Probleme mit Ihrem Code.

Eine 1: 1- Beziehung ist entweder: PK <-PK , wobei eine PK-Seite auch eine FK ist, oder PK <-FK + UC , wobei die FK-Seite eine Nicht-PK ist und eine UC hat. Ihr Code zeigt, dass Sie FK <-FK haben , da Sie beide Seiten als FK definieren, aber das ist falsch. Ich weiß, dass PIIUseres die PK-Seite und LoyaltyUserDetaildie FK-Seite ist. Dies bedeutet, PIIUserdass kein FK-Feld vorhanden ist, dies jedoch der LoyaltyUserDetailFall ist.

Wenn die 1: 1- Beziehung optional ist, muss die FK-Seite mindestens 1 nullbares Feld haben.

pswg oben hat Ihre Frage beantwortet, aber einen Fehler gemacht, dass er / sie auch eine FK in PIIUser definiert hat, was natürlich falsch ist, wie ich oben beschrieben habe. Definieren Sie also das nullfähige FK-Feld in LoyaltyUserDetail, definieren Sie das Attribut in LoyaltyUserDetail, um es als FK-Feld zu markieren, geben Sie jedoch kein FK-Feld in an PIIUser.

Sie erhalten die Ausnahme, die Sie oben unter dem Beitrag von pswg beschrieben haben, da keine Seite die PK-Seite ist (prinzipielles Ende).

EF ist nicht sehr gut im 1: 1-Bereich, da es nicht in der Lage ist, eindeutige Einschränkungen zu bewältigen. Ich bin kein Experte für Code, daher weiß ich nicht, ob ein UC erstellt werden kann oder nicht.

(Bearbeiten) Übrigens: A 1: 1 B (FK) bedeutet, dass nur 1 FK-Einschränkung erstellt wurde, wobei das Ziel von B auf die PK von A zeigt und nicht 2.

Frans Bouma
quelle
3
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);

Dadurch wird das Problem mit REFERENCE und FOREIGN KEYS gelöst

beim AKTUALISIEREN oder LÖSCHEN eines Datensatzes

pampi
quelle
Dies wird nicht wie erwartet funktionieren. Dies führt zu Migrationscode, der LoyaltyUserId nicht als Fremdschlüssel verwendet, sodass User.Id und LoyaltyUser.Id denselben Wert haben und LoyaltyUserId leer bleibt.
Aaron Queenan
2

Versuchen Sie, das ForeignKeyAttribut der LoyaltyUserDetailEigenschaft hinzuzufügen :

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

Und das PIIUserEigentum:

public class LoyaltyUserDetail
{
    ...
    public int PIIUserId { get; set; }
    [ForeignKey("PIIUserId")]
    public PIIUser PIIUser { get; set; }
    ...
}
pswg
quelle
1
Wir haben Datenanmerkungen vor all diesen fließenden API-Ansätzen ausprobiert. Datenanmerkungen funktionierten jedoch nicht. Wenn Sie oben erwähnte Datenanmerkungen hinzufügen, löst EF diese Ausnahme aus => Das Hauptende einer Zuordnung zwischen den Typen 'LoyaltyUserDetail' und 'PIIUser' kann nicht ermittelt werden. Das Hauptende dieser Zuordnung muss explizit mithilfe der API für fließende Beziehungen oder mithilfe von Datenanmerkungen konfiguriert werden.
İlkay İlknur
@ İlkayİlknur Was passiert, wenn Sie das ForeignKeyAttribut nur einem Ende der Beziehung hinzufügen ? dh nur am PIIUser oder LoyaltyUserDetail .
Pswg
Ef löst die gleiche Ausnahme aus.
İlkay İlknur
1
@pswg Warum FK bei PIIUser? LoyaltyUserDetail hat einen FK, keinen PIIUser. Es muss also [Key, ForeignKey ("PIIUser")] in der PIIUserId-Eigenschaft des LoyaltyUserDetail sein. Versuchen Sie dies
user808128
@ user808128 Sie können die Anmerkung entweder in der Navigationseigenschaft oder in der ID platzieren, ohne Unterschied.
Thomas Boby
1

Die eine Sache, die mit den obigen Lösungen verwirrend ist, ist, dass der Primärschlüssel in beiden Tabellen als " Id" definiert ist. Wenn Sie einen Primärschlüssel basierend auf dem Tabellennamen haben, der nicht funktioniert, habe ich die Klassen geändert, um dasselbe zu veranschaulichen. Das heißt, die optionale Tabelle sollte keinen eigenen Primärschlüssel definieren, sondern denselben Schlüsselnamen aus der Haupttabelle verwenden.

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

Und dann gefolgt von

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

Würde den Trick machen, die akzeptierte Lösung kann dies nicht klar erklären, und es warf mich für ein paar Stunden ab, um die Ursache zu finden

Skjagini
quelle
1

Dies ist für das Originalposter nicht von Nutzen, aber für alle, die noch auf EF6 arbeiten und benötigen, dass sich der Fremdschlüssel vom Primärschlüssel unterscheidet, gehen Sie wie folgt vor:

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

modelBuilder.Entity<PIIUser>()
    .HasRequired(t => t.LoyaltyUserDetail)
    .WithOptional(t => t.PIIUser)
    .Map(m => m.MapKey("LoyaltyUserDetailId"));

Beachten Sie, dass Sie das LoyaltyUserDetailIdFeld nicht verwenden können, da es, soweit ich das beurteilen kann, nur mit der fließenden API angegeben werden kann. (Ich habe drei Möglichkeiten ausprobiert, dies mit dem ForeignKeyAttribut zu tun , und keine davon hat funktioniert).

Aaron Queenan
quelle