1:1-Beziehungen
1:1-Beziehungen werden verwendet, wenn eine Entität höchstens einer anderen Entität zugeordnet ist. Beispielsweise verfügt ein Blog
über einen BlogHeader
und dieser BlogHeader
gehört zu einem einzelnen Blog
.
Dieses Dokument umfasst viele Beispiele. Die Beispiele beginnen mit allgemeinen Fällen, die auch Konzepte einführen. In späteren Beispielen werden weniger häufige Arten der Konfiguration behandelt. Eine gute Herangehensweise ist es, die ersten paar Beispiele und Konzepte zu verstehen und sich dann je nach Ihren speziellen Bedürfnissen den späteren Beispielen zuzuwenden. Auf der Grundlage dieses Ansatzes beginnen wir mit einfachen „erforderlichen“ und „optionalen“ 1:1-Beziehungen.
Tipp
Den Code für alle folgenden Beispiele finden Sie in OneToOne.cs.
Erforderliche 1:1-Beziehung
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public BlogHeader? Header { get; set; } // Reference navigation to dependent
}
// Dependent (child)
public class BlogHeader
{
public int Id { get; set; }
public int BlogId { get; set; } // Required foreign key property
public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}
Eine 1:1-Beziehung besteht aus:
- Mindestens einer Primär- oder Alternativschlüsseleigenschaft für die Prinzipalentität. Beispiel:
Blog.Id
. - Mindestens einer Fremdschlüsseleigenschaft für die abhängige Entität. Beispiel:
BlogHeader.BlogId
. - Optional eine Verweisnavigation für die Prinzipalentität, die auf die abhängige Entität verweist. Beispiel:
Blog.Header
. - Optional eine Verweisnavigation für die abhängige Entität, die auf die Prinzipalentität verweist. Beispiel:
BlogHeader.Blog
.
Tipp
Es ist nicht immer offensichtlich, welche Seite einer 1:1-Beziehung die Prinzipal- und welche die abhängige Entität sein sollte. Einige Überlegungen:
- Wenn die Datenbanktabellen für die beiden Typen bereits existieren, dann muss die Tabelle mit der/den Fremdschlüsselspalte(n) dem abhängigen Typ zugeordnet werden.
- Ein Typ ist normalerweise der abhängige Typ, wenn er ohne den anderen Typ logisch nicht existieren kann. Beispielsweise ist es nicht sinnvoll, eine Überschrift für einen Blog zu verwenden, den es nicht gibt, sodass
BlogHeader
natürlich der abhängige Typ ist. - Wenn es eine natürliche Beziehung zwischen übergeordneten und untergeordneten Elementen gibt, ist das untergeordnete Element in der Regel der abhängige Typ.
Für die Beziehung in diesem Beispiel gilt daher Folgendes:
- Die Fremdschlüsseleigenschaft
BlogHeader.BlogId
kann keine Nullwerte zulassen. Dadurch wird die Beziehung „erforderlich“, da jede abhängige (BlogHeader
) mit einem Prinzipal (Blog
) verbunden sein muss, da ihre Fremdschlüsseleigenschaft auf einen Wert festgelegt sein muss. - Beide Entitäten weisen Navigationen zur zugehörigen Entität auf der anderen Seite der Beziehung auf.
Hinweis
Eine erforderliche Beziehung stellt sicher, dass jede abhängige Entität einer bestimmten Prinzipalentität zugeordnet werden muss. Eine Prinzipalentität kann jedoch immer ohne abhängige Entitäten existieren. Das heißt, eine erforderliche Beziehung bedeutet nicht, dass es immer eine abhängige Entität geben wird. Es gibt keine Möglichkeit im EF-Modell und auch keine Standardmethode in einer relationalen Datenbank, um sicherzustellen, dass ein Prinzipal einer abhängigen Entität zugeordnet ist. Wenn dies erforderlich ist, muss es in der Anwendungslogik (Geschäftslogik) implementiert werden. Weitere Informationen finden Sie unter Erforderliche Navigationen.
Tipp
Eine Beziehung mit zwei Navigationen – eine von der abhängigen zur Prinzipalentität und eine umgekehrte von der Prinzipalentität zur abhängigen Entität – wird als bidirektionale Beziehung bezeichnet.
Diese Beziehung wird gemäß Konvention erkannt. Dies bedeutet:
Blog
wird als Prinzipal in der Beziehung ermittelt, währendBlogHeader
als abhängige Entität erkannt wird.BlogHeader.BlogId
wird als Fremdschlüssel der abhängigen Entität erkannt, der auf den PrimärschlüsselBlog.Id
des Prinzipals verweist. Die Beziehung wird als erforderlich erkannt, daBlogHeader.BlogId
keine Nullwerte zulässt.Blog.BlogHeader
wird als Verweisnavigation ermittelt.BlogHeader.Blog
wird als Verweisnavigation ermittelt.
Wichtig
Bei Verwendung von Nullwerte zulassenden C#-Verweistypen muss die Navigation von der abhängigen zur Prinzipalentität Nullwerte zulassen, wenn die Fremdschlüsseleigenschaft Nullwerte zulässt. Wenn die Fremdschlüsseleigenschaft „Non-Nullable“ ist, dann kann die Navigation entsprechend Nullwerte zulassen oder nicht. In diesem Fall ist BlogHeader.BlogId
„Non-Nullable“, und BlogHeader.Blog
ist auch „Non-Nullable“. Das = null!;
-Konstrukt wird verwendet, um dies für den C#-Compiler als Absicht zu kennzeichnen, da EF normalerweise die Blog
-Instanz festlegt und diese bei einer vollständig geladenen Beziehung nicht NULL sein kann. Weitere Informationen finden Sie unter Arbeiten mit Nullable-Verweistypen.
In Fällen, in denen die Navigationen, Fremdschlüssel oder die erforderliche/optionale Art der Beziehung nicht gemäß der Konvention ermittelt werden, können diese Elemente explizit konfiguriert werden. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasOne(e => e.Header)
.WithOne(e => e.Blog)
.HasForeignKey<BlogHeader>(e => e.BlogId)
.IsRequired();
}
Im obigen Beispiel beginnt die Konfiguration der Beziehungen mit dem Prinzipalentitätstyp (Blog
). Wie bei allen Beziehungen ist es genau gleichbedeutend, stattdessen mit dem abhängigen Entitätstyp (BlogHeader
) zu beginnen. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BlogHeader>()
.HasOne(e => e.Blog)
.WithOne(e => e.Header)
.HasForeignKey<BlogHeader>(e => e.BlogId)
.IsRequired();
}
Keine dieser Optionen ist besser als die andere, da beide zu genau der gleichen Konfiguration führen.
Tipp
Es ist nie erforderlich, eine Beziehung zweimal zu konfigurieren, einmal vom Prinzipal aus und dann noch einmal von der abhängigen Entität aus. Auch der Versuch, die Prinzipal- und die abhängige Hälfte einer Beziehung getrennt zu konfigurieren, funktioniert in der Regel nicht. Sie können jede Beziehung entweder von der einen oder von der anderen Seite aus konfigurieren und dann den Konfigurationscode nur einmal schreiben.
Optionale 1:1-Beziehung
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public BlogHeader? Header { get; set; } // Reference navigation to dependent
}
// Dependent (child)
public class BlogHeader
{
public int Id { get; set; }
public int? BlogId { get; set; } // Optional foreign key property
public Blog? Blog { get; set; } // Optional reference navigation to principal
}
Dies ist dasselbe wie im vorherigen Beispiel, außer dass die Fremdschlüsseleigenschaft und die Navigation zum Prinzipal jetzt Nullwerte zulassen. Dadurch wird die Beziehung „optional“, denn eine abhängige Entität (BlogHeader
) kann nicht mit einer beliebigen Prinzipalentität (Blog
) verbunden sein, indem seine Fremdschlüsseleigenschaft und die Navigation auf null
festgelegt werden.
Wichtig
Bei Verwendung von Nullwerte zulassenden C#-Verweistypen muss die Navigationseigenschaft von der abhängigen zur Prinzipalentität Nullwerte zulassen, wenn die Fremdschlüsseleigenschaft Nullwerte zulässt. In diesem Fall lässt BlogHeader.BlogId
Nullwerte zu, daher muss BlogHeader.Blog
ebenfalls Nullwerte zulassen. Weitere Informationen finden Sie unter Arbeiten mit Nullable-Verweistypen.
Wie zuvor wird diese Beziehung gemäß Konvention erkannt. In Fällen, in denen die Navigationen, Fremdschlüssel oder die erforderliche/optionale Art der Beziehung nicht gemäß der Konvention ermittelt werden, können diese Elemente explizit konfiguriert werden. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasOne(e => e.Header)
.WithOne(e => e.Blog)
.HasForeignKey<BlogHeader>(e => e.BlogId)
.IsRequired(false);
}
Erforderliche 1:1-Beziehung (Primärschlüssel zu Primärschlüssel)
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public BlogHeader? Header { get; set; } // Reference navigation to dependent
}
// Dependent (child)
public class BlogHeader
{
public int Id { get; set; }
public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}
Anders als bei 1:N-Beziehungen kann das abhängige Ende einer 1:1-Beziehung seine Primärschlüsseleigenschaft(en) als Fremdschlüsseleigenschaft(en) verwenden. Dies wird oft als PK-zu-PK-Beziehung (Primärschlüssel-zu-Primärschlüssel) bezeichnet. Dies ist nur möglich, wenn der Prinzipal und die abhängigen Typen dieselben Primärschlüsseltypen aufweisen, und die daraus resultierende Beziehung ist immer erforderlich, da der Primärschlüssel der abhängigen Entität keine Nullwerte zulassen darf (Non-Nullable).
Jede 1:1-Beziehung, bei der der Fremdschlüssel nicht gemäß Konvention ermittelt wird, muss so konfiguriert werden, dass das Prinzipal- und das abhängige Ende der Beziehung angegeben werden. Dies erfolgt in der Regel mit einem Aufruf von HasForeignKey
. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasOne(e => e.Header)
.WithOne(e => e.Blog)
.HasForeignKey<BlogHeader>();
}
Tipp
HasPrincipalKey
kann auch für diesen Zweck verwendet werden, aber das ist weniger üblich.
Wenn beim Aufruf von HasForeignKey
keine Eigenschaft angegeben wird und der Primärschlüssel geeignet ist, wird er als Fremdschlüssel verwendet. In Fällen, in denen die Navigationen, Fremdschlüssel oder die erforderliche/optionale Art der Beziehung nicht gemäß der Konvention ermittelt werden, können diese Elemente explizit konfiguriert werden. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasOne(e => e.Header)
.WithOne(e => e.Blog)
.HasForeignKey<BlogHeader>(e => e.Id)
.IsRequired();
}
Erforderliche 1:1-Beziehung mit Schattenfremdschlüssel
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public BlogHeader? Header { get; set; } // Reference navigation to dependent
}
// Dependent (child)
public class BlogHeader
{
public int Id { get; set; }
public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}
In bestimmten Fällen möchten Sie vielleicht keine Fremdschlüsseleigenschaft in Ihrem Modell verwenden, da Fremdschlüssel ein Detail zur Darstellung der Beziehung in der Datenbank sind, das nicht benötigt wird, wenn Sie die Beziehung auf rein objektorientierte Weise verwenden. Wenn Entitäten jedoch serialisiert werden sollen, z. B. um sie über eine Leitung zu versenden, dann können die Fremdschlüsselwerte eine nützliche Möglichkeit sein, die Beziehungsinformationen intakt zu halten, wenn die Entitäten nicht in einer Objektform vorliegen. Es ist daher oft pragmatisch, zu diesem Zweck Fremdschlüsseleigenschaften im .NET-Typ zu behalten. Fremdschlüsseleigenschaften können privat sein, was oft ein guter Kompromiss ist, um zu vermeiden, dass der Fremdschlüssel offengelegt wird, während sein Wert bei der Entität verbleiben kann.
In Anlehnung an das vorherige Beispiel entfernt dieses Beispiel die Fremdschlüsseleigenschaft aus dem abhängigen Entitätstyp. Anstatt jedoch den Primärschlüssel zu verwenden, wird EF angewiesen, eine Schattenfremdschlüssel-Eigenschaft namens BlogId
vom Typ int
zu erstellen.
Ein wichtiger Hinweis hier ist, dass Nullwerte zulassende C#-Verweistypen verwendet werden, sodass die NULL-Zulässigkeit der Navigation von der abhängigen zur Prinzipalentität verwendet wird, um zu bestimmen, ob die Fremdschlüsseleigenschaft Nullwerte zulässt, und somit, ob die Beziehung optional oder erforderlich ist. Wenn Nullwerte zulassende Referenztypen nicht verwendet werden, dann lässt die Schattenfremdschlüssel-Eigenschaft standardmäßig Nullwerte zu, sodass die Beziehung standardmäßig optional ist. Verwenden Sie in diesem Fall IsRequired
, um zu erzwingen, dass die Schattenfremdschlüssel-Eigenschaft „Non-Nullable“ und die Beziehung somit erforderlich ist.
Auch diese Beziehung erfordert eine gewisse Konfiguration, um das Prinzipalende und die abhängigen Enden anzugeben:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasOne(e => e.Header)
.WithOne(e => e.Blog)
.HasForeignKey<BlogHeader>("BlogId");
}
In Fällen, in denen die Navigationen, Fremdschlüssel oder die erforderliche/optionale Art der Beziehung nicht gemäß der Konvention ermittelt werden, können diese Elemente explizit konfiguriert werden. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasOne(e => e.Header)
.WithOne(e => e.Blog)
.HasForeignKey<BlogHeader>("BlogId")
.IsRequired();
}
Optionale 1:1-Beziehung mit Schattenfremdschlüssel
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public BlogHeader? Header { get; set; } // Reference navigation to dependent
}
// Dependent (child)
public class BlogHeader
{
public int Id { get; set; }
public Blog? Blog { get; set; } // Optional reference navigation to principal
}
Wie im vorigen Beispiel wurde die Fremdschlüsseleigenschaft aus dem abhängigen Entitätstyp entfernt. Im Gegensatz zum vorherigen Beispiel wird die Fremdschlüsseleigenschaft dieses Mal jedoch als „Nullwerte zulassend“ erstellt, da Nullwerte zulassende C#-Verweistypen verwendet werden und die Navigation für den abhängigen Entitätstyp entsprechend Nullwerte zulässt. Dadurch wird die Beziehung optional.
Wenn keine Nullwerte zulassenden C#-Verweistypen verwendet werden, wird die Fremdschlüsseleigenschaft standardmäßig als „Nullwerte zulassend“ erstellt. Das bedeutet, dass Beziehungen mit automatisch erstellten Schatteneigenschaften standardmäßig optional sind.
Wie zuvor muss auch diese Beziehung konfiguriert werden, um das Prinzipalende und die abhängigen Enden anzugeben:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasOne(e => e.Header)
.WithOne(e => e.Blog)
.HasForeignKey<BlogHeader>("BlogId");
}
In Fällen, in denen die Navigationen, Fremdschlüssel oder die erforderliche/optionale Art der Beziehung nicht gemäß der Konvention ermittelt werden, können diese Elemente explizit konfiguriert werden. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasOne(e => e.Header)
.WithOne(e => e.Blog)
.HasForeignKey<BlogHeader>("BlogId")
.IsRequired(false);
}
1:1-Beziehung ohne Navigation zum Prinzipal
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public BlogHeader? Header { get; set; } // Reference navigation to dependent
}
// Dependent (child)
public class BlogHeader
{
public int Id { get; set; }
public int BlogId { get; set; } // Required foreign key property
}
Für dieses Beispiel wurde die Fremdschlüsseleigenschaft wieder eingeführt, aber die Navigation zu den abhängigen Entitäten wurde entfernt.
Tipp
Eine Beziehung mit nur einer Navigation – eine von der abhängigen Entität zur Prinzipalentität oder eine von der Prinzipalentität zur abhängigen Entität, aber nicht beides – wird als unidirektionale Beziehung bezeichnet.
Diese Beziehung wird gemäß Konvention ermittelt, da der Fremdschlüssel erkannt wird und damit die abhängige Seite angibt. In Fällen, in denen die Navigationen, Fremdschlüssel oder die erforderliche/optionale Art der Beziehung nicht gemäß der Konvention ermittelt werden, können diese Elemente explizit konfiguriert werden. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasOne(e => e.Header)
.WithOne()
.HasForeignKey<BlogHeader>(e => e.BlogId)
.IsRequired();
}
Beachten Sie, dass der Aufruf von WithOne
keine Argumente aufweist. Dies ist die Möglichkeit, EF mitzuteilen, dass es keine Navigation von BlogHeader
zu Blog
gibt.
Wenn die Konfiguration von der Entität ohne Navigation ausgeht, muss der Typ der Entität am anderen Ende der Beziehung explizit angegeben werden, indem der generische Aufruf HasOne<>()
verwendet wird. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BlogHeader>()
.HasOne<Blog>()
.WithOne(e => e.Header)
.HasForeignKey<BlogHeader>(e => e.BlogId)
.IsRequired();
}
1:1-Beziehung ohne Navigation zum Prinzipal und mit Schattenfremdschlüssel
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public BlogHeader? Header { get; set; } // Reference navigation to dependent
}
// Dependent (child)
public class BlogHeader
{
public int Id { get; set; }
}
In diesem Beispiel werden zwei der vorherigen Beispiele kombiniert, indem sowohl die Fremdschlüsseleigenschaft als auch die Navigation für die abhängige Entität entfernt werden.
Wie zuvor muss auch diese Beziehung konfiguriert werden, um das Prinzipalende und die abhängigen Enden anzugeben:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasOne(e => e.Header)
.WithOne()
.HasForeignKey<BlogHeader>("BlogId")
.IsRequired();
}
Eine vollständigere Konfiguration kann verwendet werden, um die Navigation und den Fremdschlüsselnamen explizit zu konfigurieren, je nach Bedarf mit einem entsprechenden Aufruf von IsRequired()
oder IsRequired(false)
. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasOne(e => e.Header)
.WithOne()
.HasForeignKey<BlogHeader>("BlogId")
.IsRequired();
}
1:1-Beziehung ohne Navigation zur abhängigen Entität
// Principal (parent)
public class Blog
{
public int Id { get; set; }
}
// Dependent (child)
public class BlogHeader
{
public int Id { get; set; }
public int BlogId { get; set; } // Required foreign key property
public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}
In den beiden vorangegangenen Beispielen gab es Navigationen vom Prinzipal zur abhängigen Entität, aber keine Navigation von der abhängigen Entität zum Prinzipal. Bei den nächsten Beispielen wird die Navigation zur abhängigen Entität wieder eingeführt, während stattdessen die Navigation zum Prinzipal entfernt wird.
Gemäß Konvention behandelt EF dies als 1:N-Beziehung. Für die 1:1-Beziehung ist eine minimale Konfiguration erforderlich:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BlogHeader>()
.HasOne(e => e.Blog)
.WithOne();
}
Beachten Sie erneut, dass WithOne()
ohne Argumente aufgerufen wird, um anzuzeigen, dass es keine Navigation in diese Richtung gibt.
In Fällen, in denen die Navigationen, Fremdschlüssel oder die erforderliche/optionale Art der Beziehung nicht gemäß der Konvention ermittelt werden, können diese Elemente explizit konfiguriert werden. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<BlogHeader>()
.HasOne(e => e.Blog)
.WithOne()
.HasForeignKey<BlogHeader>(e => e.BlogId)
.IsRequired();
}
Wenn die Konfiguration von der Entität ohne Navigation ausgeht, muss der Typ der Entität am anderen Ende der Beziehung explizit angegeben werden, indem der generische Aufruf HasOne<>()
verwendet wird. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasOne<BlogHeader>()
.WithOne(e => e.Blog)
.HasForeignKey<BlogHeader>(e => e.BlogId)
.IsRequired();
}
1:1-Beziehung ohne Navigation
Gelegentlich kann es sinnvoll sein, eine Beziehung ohne Navigationen zu konfigurieren. Eine solche Beziehung kann nur bearbeitet werden, indem der Fremdschlüsselwert direkt geändert wird.
// Principal (parent)
public class Blog
{
public int Id { get; set; }
}
// Dependent (child)
public class BlogHeader
{
public int Id { get; set; }
public int BlogId { get; set; } // Required foreign key property
}
Diese Beziehung wird nicht gemäß Konvention erkannt, da es keine Hinweise darauf gibt, dass die beiden Typen miteinander verknüpft sind. Sie kann explizit in OnModelCreating
konfiguriert werden. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasOne<BlogHeader>()
.WithOne();
}
Bei dieser Konfiguration wird die Eigenschaft BlogHeader.BlogId
immer noch gemäß Konvention als Fremdschlüssel erkannt und die Beziehung ist „erforderlich“, da die Fremdschlüsseleigenschaft keine Nullwerte zulässt. Die Beziehung kann als „optional“ festgelegt werden, indem dafür gesorgt wird, dass die Fremdschlüsseleigenschaft Nullwerte zulässt.
Eine vollständigere explizite Konfiguration dieser Beziehung lautet:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasOne<BlogHeader>()
.WithOne()
.HasForeignKey<BlogHeader>(e => e.BlogId)
.IsRequired();
}
1:1-Beziehung mit Alternativschlüssel
In allen bisherigen Beispielen ist die Fremdschlüsseleigenschaft der abhängigen Entität auf die Primärschlüsseleigenschaft des Prinzipals beschränkt. Der Fremdschlüssel kann stattdessen auf eine andere Eigenschaft beschränkt werden, die dann zu einem Alternativschlüssel für den Typ der Prinzipalentität wird. Beispiel:
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public int AlternateId { get; set; } // Alternate key as target of the BlogHeader.BlogId foreign key
public BlogHeader? Header { get; set; } // Reference navigation to dependent
}
// Dependent (child)
public class BlogHeader
{
public int Id { get; set; }
public int BlogId { get; set; } // Required foreign key property
public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}
Diese Beziehung wird nicht gemäß Konvention entdeckt, da EF gemäß Konvention immer eine Beziehung zum Primärschlüssel erstellt. Sie kann explizit in OnModelCreating
mithilfe eines Aufrufs von HasPrincipalKey
konfiguriert werden. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasOne(e => e.Header)
.WithOne(e => e.Blog)
.HasPrincipalKey<Blog>(e => e.AlternateId);
}
HasPrincipalKey
kann mit anderen Aufrufen kombiniert werden, um die Navigationen, die Fremdschlüsseleigenschaften und den erforderlichen/optionalen Charakter explizit zu konfigurieren. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasOne(e => e.Header)
.WithOne(e => e.Blog)
.HasPrincipalKey<Blog>(e => e.AlternateId)
.HasForeignKey<BlogHeader>(e => e.BlogId)
.IsRequired();
}
1:1-Beziehung mit zusammengesetzten Fremdschlüsseln
In allen bisherigen Beispielen bestand die Primär- oder Alternativschlüsseleigenschaft des Prinzipals aus einer einzelnen Eigenschaft. Primär- oder Alternativschlüssel können auch aus mehr als einer Eigenschaft gebildet werden – diese werden als „zusammengesetzte Schlüssel“ bezeichnet. Wenn der Prinzipal einer Beziehung einen zusammengesetzten Schlüssel aufweist, dann muss auch der Fremdschlüssel der abhängigen Entität ein zusammengesetzter Schlüssel mit der gleichen Anzahl von Eigenschaften sein. Beispiel:
// Principal (parent)
public class Blog
{
public int Id1 { get; set; } // Composite key part 1
public int Id2 { get; set; } // Composite key part 2
public BlogHeader? Header { get; set; } // Reference navigation to dependent
}
// Dependent (child)
public class BlogHeader
{
public int Id { get; set; }
public int BlogId1 { get; set; } // Required foreign key property part 1
public int BlogId2 { get; set; } // Required foreign key property part 2
public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}
Diese Beziehung wird gemäß Konvention erkannt. Sie wird jedoch nur erkannt, wenn der zusammengesetzte Schlüssel explizit konfiguriert wurde, da zusammengesetzte Schlüssel nicht automatisch erkannt werden. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasKey(e => new { e.Id1, e.Id2 });
}
Wichtig
Ein zusammengesetzter Fremdschlüsselwert wird als null
betrachtet, wenn einer seiner Eigenschaftswerte NULL ist. Ein zusammengesetzter Fremdschlüssel, bei dem eine Eigenschaft NULL und eine andere nicht NULL ist, wird nicht als Übereinstimmung mit einem Primär- oder Alternativschlüssel mit denselben Werten betrachtet. Beide werden als null
betrachtet.
Sowohl HasForeignKey
als auch HasPrincipalKey
können verwendet werden, um explizit Schlüssel mit mehreren Eigenschaften anzugeben. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>(
nestedBuilder =>
{
nestedBuilder.HasKey(e => new { e.Id1, e.Id2 });
nestedBuilder.HasOne(e => e.Header)
.WithOne(e => e.Blog)
.HasPrincipalKey<Blog>(e => new { e.Id1, e.Id2 })
.HasForeignKey<BlogHeader>(e => new { e.BlogId1, e.BlogId2 })
.IsRequired();
});
}
Tipp
Im obigen Code wurden die Aufrufe von HasKey
und HasOne
in einem geschachtelten Generator zusammengefasst. Geschachtelte Generatoren beseitigen die Notwendigkeit, Entity<>()
mehrfach für denselben Entitätstyp aufzurufen, sind aber funktional äquivalent zum mehrfachen Aufruf von Entity<>()
.
Erforderliche 1:1-Beziehung ohne kaskadierendes Delete
// Principal (parent)
public class Blog
{
public int Id { get; set; }
public BlogHeader? Header { get; set; } // Reference navigation to dependent
}
// Dependent (child)
public class BlogHeader
{
public int Id { get; set; }
public int BlogId { get; set; } // Required foreign key property
public Blog Blog { get; set; } = null!; // Required reference navigation to principal
}
Gemäß Konvention sind die erforderlichen Beziehungen so konfiguriert, dass sie ein kaskadierendes Delete verwenden. Das liegt daran, dass die abhängige Entität in der Datenbank nicht mehr existieren kann, sobald der Prinzipal gelöscht wurde. Die Datenbank kann so konfiguriert werden, dass sie einen Fehler generiert, der in der Regel zum Absturz der Anwendung führt, anstatt abhängige Zeilen, die nicht länger existieren können, automatisch zu löschen. Dies erfordert eine gewisse Konfiguration:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.HasOne(e => e.Header)
.WithOne(e => e.Blog)
.OnDelete(DeleteBehavior.Restrict);
}
Auf sich selbst verweisende 1:1-Beziehung
In allen vorherigen Beispielen war der Prinzipalentitätstyp ein anderer als der Typ der abhängigen Entität. Das muss nicht der Fall sein. Beispielsweise ist in den folgenden Typen jede Person
optional mit einer anderen Person
verknüpft.
public class Person
{
public int Id { get; set; }
public int? HusbandId { get; set; } // Optional foreign key property
public Person? Husband { get; set; } // Optional reference navigation to principal
public Person? Wife { get; set; } // Reference navigation to dependent
}
Diese Beziehung wird gemäß Konvention erkannt. In Fällen, in denen die Navigationen, Fremdschlüssel oder die erforderliche/optionale Art der Beziehung nicht gemäß der Konvention ermittelt werden, können diese Elemente explizit konfiguriert werden. Beispiel:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Person>()
.HasOne(e => e.Husband)
.WithOne(e => e.Wife)
.HasForeignKey<Person>(e => e.HusbandId)
.IsRequired(false);
}
Hinweis
Da bei 1:1-Beziehungen, die auf sich selbst verweisen, der Typ der Prinzipal- und der abhängigen Entität derselbe ist, bringt die Angabe, welcher Typ den Fremdschlüssel enthält, keine Klarheit über das abhängige Ende. In diesem Fall zeigt die in HasOne
angegebene Navigation von der abhängigen Entität zum Prinzipal, und die in WithOne
angegebene Navigation vom Prinzipal zur abhängigen Entität.