Dieser Artikel wurde maschinell übersetzt.

Datenpunkte

Umgang mit fehlenden Fremdschlüsseln

Julie Lerman

Julie LermanIn diesem Monat schreibe ich über ein Thema, fand ich mich helfen Menschen mit häufig spät haben: Probleme mit verknüpften Klassen definiert in Code zuerst und dann, in den meisten Fällen, das Model-View-Controller (MVC) Framework verwendet.Die Probleme, die Entwickler erlebt haben sind nicht spezifisch für Code erste.Sie sind das Ergebnis des zugrunde liegenden Verhaltens der Entity Framework (EF) und in der Tat, für die meisten objektrelationale Mapper (ORMs).Aber es scheint, dass das Problem auftauchen wird, da Entwickler Code zuerst mit bestimmten Erwartungen kommen.MVC verursacht sie Schmerzen wegen der sehr getrennte Natur.

Anstatt nur zeigen Ihnen den richtigen Code, werde ich diese Spalte verwenden, können Sie das EF-Verhalten zu verstehen, so dass Sie können dieses Wissen anwenden, für die viele Szenarios, die auftreten können, wenn Ihre Klassen entwerfen oder Schreiben Ihrer Daten Zugangscode mit EF APIs.

Code ersten Konvention ist in der Lage zu erkennen und zu verschiedenen Beziehungen mit verschiedenen Kombinationen von Eigenschaften in den Klassen nicht korrekt herleiten.In diesem Beispiel sind die ich schreibe, wie die Blätter Bäume in der Nähe von meinem Haus in Vermont spektakuläre Farben einschalten, ich verwende Baum und Blatt als meine verwandten Klassen.Für eine eins-zu-viele-Beziehung ist der einfachste Weg, Sie könnte beschreiben, die in Ihren Klassen und Code ersten Ihre Absicht erkennen eine Navigationseigenschaft in Tree-Klasse haben, die irgendeine Art von Auflistung Leaf Typen darstellt.Die Leaf-Klasse benötigt keine Eigenschaften auf Baum.Abbildung 1 zeigt die Struktur und Blatt-Klasse.

Abbildung 1 ähnliche Struktur und Leaf Klassen

public class Tree
{
  public Tree()
  {
    Leaves = new List<Leaf>();
  }
  public int TreeId { get; set; }
  public string Type { get; set; }
  public double Lat { get; set; }
  public double Long { get; set; }
  public string Notes { get; set; }
  public ICollection<Leaf> Leaves { get; set; }
}
public class Leaf
{
  public int LeafId { get; set; }
  public DateTime FellFromTreeDate { get; set; }
  public string FellFromTreeColor { get; set; }
}

Gemäß der Konvention wissen Code erste, dass ein Fremdschlüssel in der Leaf-Tabelle in der Datenbank erforderlich ist. Sie Qualitätssicherungssystemen ein Fremdschlüsselfeld Name "Tree_TreeId", und diese Angaben in den Metadaten erstellt von ersten Code zur Laufzeit, EF wird verstehen, wie Abfragen und Aktualisierungen mithilfe dieser Fremdschlüssel auszuarbeiten. EF nutzt dieses Verhalten, indem Sie sich auf den gleichen Prozess mit "unabhängige Vereinigungen verwendet" — die einzige Art von Zuordnung wir vor dem Microsoft können.NET Framework 4 — die keine Fremdschlüsseleigenschaft in der abhängigen Klasse erfordern.

Auf diese Weise eine schön, sauber, die Klassen zu definieren, wenn Sie sicher sind, dass Sie keine jemals von einem Blatt zurück zu den Baum in der Anwendung navigieren müssen. Ohne direkten Zugriff auf den Fremdschlüssel müssen Sie jedoch zusätzliche fleißig werden beim Codieren.

Erstellen neuer abhängige Typen ohne Foreign Key- oder Navigationseigenschaften

Obwohl Sie einfach diese Klassen verwenden können, um ein Baum und seine Blätter in Ihren ASP anzuzeigen.NET MVC-Anwendung und bearbeiten Blätter, Entwickler oft Probleme erstellen neue Blätter in einer in der Regel konzipiert MVC-Anwendung auftreten. Früher habe ich die Vorlage aus dem Paket MVCScaffolding NuGet (mvcscaffolding.codeplex.com), damit Visual Studio automatisch­matisch meine Controller, Ansichten und einfache Repositorys erstellen, indem Sie auswählen "MvcScaffolding: Controller mit Lese-/Schreibzugriff Aktion und Aussicht, Repositories verwenden." Beachten Sie, dass da es keine Fremdschlüsseleigenschaft in der Leaf-Klasse gibt, die Gerüste Vorlagen nicht die eins-zu-viele-Beziehung erkennt. Ich habe einige kleineren Änderungen an die Ansichten und Controller, damit einen Benutzer navigieren von einem Baum zu seine Blätter, die Sie im Download Beispielcodes sehen können.

Die postback Aktion erstellen für Leaf nimmt das Blatt aus der Ansicht erstellen zurückgegeben und erzählt das Repository hinzufügen und dann speichern Sie es, wie in gezeigt Abbildung 2.

Abbildung 2 hinzufügen und speichern Blatt im Repository

[HttpPost]
public ActionResult Create(Leaf leaf)
{
  if (ModelState.IsValid)
  {
    leafRepository.InsertOrUpdate(leaf);
    leafRepository.Save();
    return RedirectToAction("Index", 
      new { treeId = Request.Form["TreeId"] });
  }
  else
  {
    return View();
  }
}

Das Repository wird das Blatt, überprüft, ob es neu ist und wenn ja, fügt es die Kontextinstanz, die als Ergebnis das Postback erstellt wurde:

public void InsertOrUpdate(Leaf leaf,int treeId){
  if (leaf.LeafId == default(int)) {
    // New entity
    context.Leaves.Add(leaf);
  } else {
    // Existing entity
    context.Entry(leaf).State = EntityState.Modified;
  }
}

Wenn speichern aufgerufen wird, erstellt EF einen Insert-Befehl, der Datenbank das neue Blatt hinzu:

    exec sp_executesql N'insert [dbo].[Leaves]([FellFromTreeDate], [FellFromTreeColor],
    [Tree_TreeId]) values (@0, @1, null)
    select [LeafId]
    from [dbo].[Leaves]
    where @@ROWCOUNT > 0 and [LeafId] = scope_identity()',
    N'@0 datetime2(7),@1 nvarchar(max) ',
    @0='2011-10-11 00:00:00',@1=N'Pale Yellow'

Beachten Sie die Werte in der zweiten Zeile des Befehls übergeben: @ 0 (für das Datum); 1 (für die geänderte Farbe); und Null. Der null-Wert ist für das Tree_TreeId-Feld bestimmt. Denken Sie daran, dass die schöne, saubere Blatt-Klasse verfügt über keine Fremdschlüsseleigenschaft um die TreeId darzustellen, so es keine Möglichkeit gibt, diesen Wert in übergeben, wenn ein Standalone-Blatt zu erstellen.

Wenn der abhängige Typ (in diesem Fall Leaf) keine Kenntnis von den Prinzipaltyp (Baum) hat, gibt es nur einen Weg zu tun, eine INSERT-Anweisung: Die Leaf-Instanz und die Instanz Struktur müssen auf den Kontext zusammen als Teil des im selben Graphen hinzugefügt werden. Das bieten EF alle Informationen, die es braucht, um den richtigen Wert zum Einfügen in die Datenbank foreign Key (z. B. Tree_TreeId) auszuarbeiten. Aber in diesem Fall wo arbeiten Sie nur mit dem Blatt, es gibt keine Informationen im Arbeitsspeicher für EF um den Wert des Baumes Key-Eigenschaft bestimmen.

Wenn Sie eine Fremdschlüsseleigenschaft in der Leaf-Klasse hatte, wäre Leben so viel einfacher. Es ist nicht allzu schwer, einen einzelnen Wert auf der hand zu halten beim Verschieben zwischen Controller und Ansichten. In der Tat, wenn Sie suchen, an die Create-Aktion in Abbildung 2, Sie können sehen, dass die Methode Zugriff auf den Wert der TreeId hat für die das Blatt erstellt wird.

Es gibt eine Reihe von Möglichkeiten zum Übergeben von Daten um in MVC-Anwendungen. Ich entschied mich für das einfachste für diese Demo: Füllung der TreeId in der MVC-ViewBag und Html.Hidden Felder nutzen, soweit erforderlich. Dadurch wird den Wert als eines der Ansicht Request.Form Elemente verfügbar.

Da ich Zugriff auf die TreeId haben, bin ich in der Lage, den Baum/Leaf-Objektdiagramms, das den TreeId für den Insert-Befehl zur Verfügung stellt. Eine schnelle Änderung der Repository-Klasse kann die InsertOrUpdate-Methode die TreeId-Variable aus der Ansicht zu akzeptieren und die Baum-Instanz mit die DbSet.Find-Methode aus der Datenbank abruft. Hier ist der betroffene Teil der Methode:

public void InsertOrUpdate(Leaf leaf,int treeId)
{
  if (leaf.LeafId == default(int)) {
    var tree=context.Trees.Find(treeId);
    tree.Leaves.Add(leaf);
  }
...

Kontext verfolgt nun die Struktur und ist sich bewusst, dass ich das Blatt in der Struktur hinzufüge. Dieses Mal, wenn Kontext.SaveChanges aufgerufen wird, EF ist in der Lage, aus dem Blatt navigieren zu dem Baum und entdecken den Schlüsselwert in der Insert-Befehl verwenden.

Abbildung 3 zeigt den geänderten Controller-Code mit der neuen Version des InsertOrUpdate.

Abbildung 3 die neue Version von InsertOrUpdate

[HttpPost]
public ActionResult Create(Leaf leaf)
{
  if (ModelState.IsValid)
  {
    leafRepository.InsertOrUpdate(leaf);
    leafRepository.Save();
    return RedirectToAction("Index",
      new { treeId = Request.Form["TreeId"] });
  }
  else
  {
    return View();
  }
}
[HttpPost]
public ActionResult Create(Leaf leaf)
{
  if (ModelState.IsValid)
  {
    var _treeId = Request.Form["TreeId"] as int;
    leafRepository.InsertOrUpdate(leaf, _treeId);
    leafRepository.Save();
    return RedirectToAction("Index", new { treeId = _treeId });
  }
  else
  {
    return View();
  }
}

Mit diesen Änderungen hat die Insert-Methode schließlich den Wert für den Fremdschlüssel, die Sie in dem Parameter mit dem Namen "2" sehen können:

exec sp_executesql N'insert [dbo].[Leaves]([FellFromTreeDate], [FellFromTreeColor], [Tree_TreeId])
values (@0, @1, @2)
select [LeafId]
from [dbo].[Leaves]
where @@ROWCOUNT > 0 and [LeafId] = scope_identity()',
N'@0 datetime2(7),@1 nvarchar(max) ,
@2 int',@0='2011-10-12 00:00:00',@1=N'Orange-Red',@2=1

Am Ende zwingt diese Problemumgehung mich zu einer anderen Reise an der Datenbank vornehmen. Dies ist der Preis, den ich werde in diesem Szenario bezahlen, wo ich die Fremdschlüsseleigenschaft in meiner abhängigen Klasse wollen nicht, möchten.

Probleme mit Updates, wenn keine Fremdschlüssel vorhanden ist

Es gibt andere Möglichkeiten, die Sie sich in eine Ecke zeichnen können, wenn Sie gebundenen und entschlossen nicht zu ausländische wichtige Eigenschaften in den Klassen sind. Hier ist ein weiteres Beispiel.

Ich werde eine neue Domain-Klasse mit dem Namen TreePhoto hinzufügen. Weil ich nicht von dieser Klasse zurück zur Struktur navigieren möchten, gibt es keine Navigationseigenschaft, und wieder, ich verfolge das Muster wo ich nicht verwenden eine Fremdschlüsseleigenschaft:

[Table("TreePhotos")]
public class TreePhoto
{
  public int Id { get; set; }
  public Byte[] Photo { get; set; }
  public string Caption { get; set; }
}

Tree-Klasse stellt die einzige Verbindung zwischen den beiden Klassen sowie ich angeben, dass jeder Baum ein Foto haben muss. Hier ist die neue Eigenschaft, die ich der Tree-Klasse hinzugefügt:

[Required]
  public TreePhoto Photo { get; set; }

Dies lässt die Möglichkeit der verwaisten Fotos, aber ich verwende in diesem Beispiel, weil ich es eine Anzahl von Zeiten gesehen — zusammen mit bitten um Hilfe — so ich an ihn richten wollte.

Wieder einmal Code ersten Konvention abschrecken­abgebaut, dass eine Fremdschlüsseleigenschaft in der Datenbank benötigt und einen Photo_Id, in meinem Namen erstellt werden würde. Beachten Sie, dass es keine NULL-Werte zulässt. Das ist, weil die Leaf.Photo Eigenschaft erforderlich ist (siehe Abbildung 4).

Using Code First Convention, Tree Gets a Non-Nullable Foreign Key to TreePhotos
Abbildung 4 verwenden Code ersten Konvention, Baumes Ruft eine NULL-Fremdschlüssel zur TreePhotos

Ihre Anwendung möglicherweise können Sie Bäume erstellen, bevor die Fotos ergriffen wurden, aber die Struktur muss noch die Foto-Eigenschaft aufgefüllt werden. Ich füge Logik in die Baum-Repository einfügen­OrUpdate Methode erstellt standardmäßig leere Foto für neue Bäume, wenn eine nicht angegeben wird:

public void InsertOrUpdate(Tree tree)
{
  if (tree.TreeId == default(int)) {
    if (tree.Photo == null)
    {
      tree.Photo = new TreePhoto { Photo = new Byte[] { 0 },
                                   Caption = "No Photo Yet" };
    }
    context.Trees.Add(tree);
}
...

Das größere Problem will ich hier konzentrieren ist wie dieses Problem Aktualisierungen betrifft. Stellen Sie sich vor, dass ein Baum und seine erforderliche Foto bereits in der Datenbank gespeichert. Sie möchten in der Lage, eine Struktur bearbeiten und haben keine Notwendigkeit für die Interaktion mit dem Foto. Sie werden abrufen die Struktur, vielleicht mit Code, z. B. "Kontext.Tre­­es.Find(someId)." Wenn es Zeitrechnung ist zu speichern, erhalten Sie einen Validierungsfehler, da Baum ein Foto erforderlich ist. Aber Baum hat ein Foto! Es ist in der Datenbank! Was ist los?

Hier ist das Problem: Wenn Sie zuerst ausführen eine Abfrage zum Abrufen der Tabelle ignorieren das verwandte Foto, nur die skalare Werte der Struktur aus der Datenbank zurückgegeben werden und Foto wird null sein (siehe Abbildung 5).

A Tree Instance Retrieved from the Database Without Its Photo
Abbildung 5 Struktur Instanz aus der Datenbank ohne seine Foto abgerufen

Das MVC-ModelBinder und EF haben die Fähigkeit, die erforderliche Anmerkung zu validieren. Wenn es Zeit zum Speichern der bearbeiteten Struktur ist, wird seine Foto noch null sein. Wenn Sie MVC seine ModelState.IsValid die Überprüfung in der Controller-Code Vermietung sind, wird erkannt, dass Foto fehlt. IsValid ist false sein, und der Controller wird nicht einmal die Mühe Aufruf des Repository. In meiner app habe ich die Validierung Modelbinder entfernt, so ließ ich mein Repository-Code für eine serverseitige Validierung verantwortlich sein können. Wenn das Repository SaveChanges aufgerufen wird, EF Validierung erkennt das fehlende Foto und eine Ausnahme ausgelöst. Aber im Repository, wir haben die Möglichkeit, das Problem zu behandeln.

Wenn die Tree-Klasse eine Fremdschlüsseleigenschaft hatte — z. B. Int PhotoId — das war benötigt (so dass Sie die Anforderung an die Navigationseigenschaft Foto entfernen), der Fremdschlüsselwert aus der Datenbank würde haben wurden verwendet, um die ausgewählte Eigenschaft der Baum-Instanz aufzufüllen. Der Baum wäre gültig, und SaveChanges wäre in der Lage, den Update-Befehl an die Datenbank senden. Mit anderen Worten, gäbe es eine Fremdschlüsseleigenschaft, wäre der Baum auch ohne die Foto-Instanz gültig gewesen.

Aber ohne den Fremdschlüssel, müssen Sie wieder einen Mechanismus für die Bereitstellung von des Fotos vor dem Speichern der Änderungen. Haben Sie Ihren Code ersten Klassen und Kontext eingerichtet, um verzögertes Laden ausführen, bewirkt jede Erwähnung des Fotos in Ihrem Code EF die Instanz aus der Datenbank geladen. Ich bin immer noch ein wenig altmodisch, wenn es darum, zu faul geht, laden, so dass meine persönliche Wahl wäre wahrscheinlich ein explizites Laden aus der Datenbank auszuführen. Die neue Zeile des Codes (die letzte Zeile im folgenden Beispiel, wo ich Load fordere) verwendet die DbContext-Methode zum Laden von verwandter Daten.

public void InsertOrUpdate(Tree tree)
{
  if (tree.TreeId == default(int)) {
  ...
} else {
    context.Entry(tree).State = EntityState.Modified;
    context.Entry(tree).Reference(t => t.Photo).Load();
  }
}

Dies macht EF glücklich. Baum überprüft, weil Foto es gibt, und EF wird ein Update für die geänderte Struktur an die Datenbank senden. Der Schlüssel hier ist, dass müssen Sie sicherstellen, dass das Foto nicht null ist; Ich habe Ihnen gezeigt, eine Möglichkeit, diese Einschränkung erfüllen.

Ein Punkt Vergleich

Wenn die Tree-Klasse einfach eine ausgewählte Eigenschaft, nichts davon wäre notwendig. Eine unmittelbare Wirkung der PhotoId Int-Eigenschaft ist, dass die Foto-Eigenschaft nicht mehr die erforderliche Anmerkung muss. Als Werttyp, es muss immer einen Wert, erfüllen die Anforderung, dass ein Baum ein Foto haben muss, auch wenn sie ist nicht als eine Instanz. Solange in PhotoId ein Wert vorhanden ist, wird die Anforderung erfüllt sein, damit der folgende Code funktioniert:

public class Tree
{
  // ...
Other properties
  public int PhotoId { get; set; }
  public TreePhoto Photo { get; set; }
}

Wenn der Controller Edit-Methode einen Baum aus der Datenbank abruft, wird die ausgewählte skalare Eigenschaft gefüllt werden. So lange, wie Sie erzwingen, dass MVC (oder was auch immer Anwendungsframework verwenden Sie) für den Roundtrip Wert, wenn es Zeit ist, aktualisieren Sie den Baum, EF wird sein wenig Gedanken über die null Foto-Eigenschaft.

Einfacher, aber keine Magie

Obwohl das EF-Team weitere API-Logik zu helfen, mit getrennten Szenarien zur Verfügung gestellt hat, ist es immer noch Ihre Aufgabe zu verstehen, wie EF funktioniert und was sind ihre Erwartungen, wenn Sie Daten, um verschieben möchten. Codierung ist ja, viel einfacher, wenn Sie Fremdschlüssel in Ihren Klassen enthalten, aber Sie Ihre Klassen und du bist der beste Richter was sollte und sollte nicht in ihnen. Dennoch, wenn Ihr Code meine Aufgabe war, würde ich sicherlich zwingen Sie um mich zu überzeugen, dass Ihre Gründe für den Ausschluss ausländischer Schlüsseleigenschaften die Vorteile der Aufnahme aufgewogen. EF wird Teil der Arbeit für Sie tun, wenn der Fremdschlüssel vorhanden sind. Aber wenn sie abwesend, so lange sind, wie Sie verstehen was EF erwartet und wie Sie diese Erwartungen erfüllen, sollte Sie in der Lage, Ihre getrennten Anwendungen auf die Art und Weise Verhalten, die Sie wollen.

Julie Lerman ist ein Microsoft MVP.NET Mentor und Berater lebt in den Hügeln von Vermont. Finden Sie ihre Präsentation auf Datenzugriff und anderen Microsoft.NET-Themen auf Benutzergruppen und Konferenzen auf der ganzen Welt. She Blogs auf thedatafarm.com/blog und ist der Autor von "Programming Entity Framework" (2010) und "Programming Entity Framework: Code-First"(2011), beide von O' Reilly Media. Folgen sie auf Twitter bei twitter.com/julielerman.

Dank der folgenden technischen Experten für die Überprüfung dieses Artikels: Jeff Derstadt und Rick Strahl