2009-05-23 2 views
2

Ich habe zwei Tabellen in meiner Datenbank durch Fremdschlüssel verbunden: Seite (PageId, andere Daten) und PageTag (PageId, Tag). Ich habe LINQ verwendet, um Klassen für diese Tabellen zu generieren, wobei die Seite als übergeordnetes Element und das Tag als untergeordnete Auflistung fungiert (Beziehung zwischen eins und mehreren). Gibt es eine Möglichkeit, PageTag-Datensätze in der Page-Klasse zum Löschen aus der Datenbank zu markieren?Wie lösche ich Datensätze aus einer untergeordneten Sammlung in LINQ to SQL?

Schnell clearification:

Ich möchte, dass die untergeordneten Objekte gelöscht werden, wenn die Eltern Datacontext SubmitChanges() aufruft, nicht vor. Ich möchte, dass sich TagString genau wie alle anderen Eigenschaften des Page-Objekts verhält.

Ich möchte Code aktivieren wie folgt aus:

Page page = mDataContext.Pages.Where(page => page.pageId = 1); 
page.TagString = "new set of tags"; 

//Changes have not been written to the database at this point. 

mDataContext.SubmitChanges(); 

//All changes should now be saved to the database. 

Hier ist meine Situation im Detail ist:
Um mit der Sammlung von Tags einfacher zu machen, ich habe eine Eigenschaft der addierte page-Objekt, das den Tag Sammlung als String behandelt:

public string TagString { 
    get { 
     StringBuilder output = new StringBuilder(); 
     foreach (PageTag tag in PageTags) { 
      output.Append(tag.Tag + " "); 
     } 

     if (output.Length > 0) { 
      output.Remove(output.Length - 1, 1); 
     } 

     return output.ToString(); 
    } 
    set { 
     string[] tags = value.Split(' '); 
     PageTags.Clear(); 
     foreach (string tag in tags) { 
      PageTag pageTag = new PageTag(); 
      pageTag.Tag = tag; 
      PageTags.Add(pageTag); 
     } 
    } 
} 

Grundsätzlich ist die Idee ist, dass, wenn eine Reihe von Tags zu diesem Objekt gesendet wird, werden die aktuellen Tags des Objekts werden gelöscht und ein neuer Satz erzeugt in ihr Platz.

Das Problem, das ich bin zu begegnen ist, dass diese Linie:

PageTags.Clear(); 

eigentlich nicht die alten Tags aus der Datenbank gelöscht, wenn Änderungen vorgelegt werden.

Um zu sehen, scheint die "richtige" Möglichkeit, Dinge zu löschen, die DeleteOnSubmit-Methode der Datenkontextklasse aufzurufen. Aber ich habe scheinbar keinen Zugriff auf die DataContext-Klasse innerhalb der Page-Klasse.

Kennt jemand eine Möglichkeit, die untergeordneten Elemente zum Löschen aus der Datenbank innerhalb der Klasse Page zu markieren?

Antwort

5

Nach ein paar weiteren Recherchen glaube ich, dass ich es geschafft habe, eine Lösung zu finden. Das Markieren eines Objekts zum Löschen, wenn es aus einer Sammlung entfernt wird, wird durch den Parameter DeleteOnNull des Association-Attributs gesteuert.

Dieser Parameter wird auf "true" gesetzt, wenn die Beziehung zwischen zwei Tabellen mit OnDelete Cascade markiert ist.

Leider gibt es keine Möglichkeit, dieses Attribut innerhalb des Designers festzulegen, und keine Möglichkeit, es innerhalb der partiellen Klasse in der * DataContext.cs-Datei festzulegen. Die einzige Möglichkeit, es zu setzen, ohne überlappende Löschvorgänge zu aktivieren, besteht darin, die Datei * DataContext.designer.cs manuell zu bearbeiten.

In meinem Fall bedeutete dies die Seite Verein zu finden, und das Hinzufügen der DeleteOnNull Eigenschaft:

[Association(Name="Page_PageTag", Storage="_Page", ThisKey="PageId", OtherKey="iPageId", IsForeignKey=true)] 
public Page Page 
{ 
    ... 
} 

und das Hinzufügen des DeleteOnNull Attribut:

[Association(Name="Page_PageTag", Storage="_Page", ThisKey="PageId", OtherKey="iPageId", IsForeignKey=true, DeleteOnNull = true)] 
public Page Page 
{ 
    ... 
} 

Beachten Sie, dass das Attribut hinzugefügt werden muß die Page-Eigenschaft der PageTag-Klasse, nicht umgekehrt. auch

Siehe:
Beth Massi -- LINQ to SQL and One-To-Many Relationships
Dave Brace -- LINQ to SQL: DeleteOnNull

+0

Aaron, danke für die Einsicht. Aber wäre es nicht einfacher, zwei Zeilen Code in Ihr Repository zu schreiben, um die Datensätze zu löschen? –

+0

Es sind nicht zwei Zeilen Code im Repository. Ich könnte eine UpdateTags (Page-Seite, String-TagString) -Methode zum Repository hinzufügen, aber das würde nicht so sauber mit Modellbindung arbeiten. – AaronSieb

+0

Dies ist auch ziemlich einfach einzurichten, jetzt, da ich weiß, dass es mit der Option "Überlappung löschen" verknüpft ist, und die manuelle Bearbeitung der Designerdatei umgehen kann. – AaronSieb

0

Haben Sie eine Beziehung in Ihrem Linq-SQL-Entity-Diagramm, die die Page- und PageTags-Tabellen verknüpft? Wenn Sie dies nicht tun, können Sie die PageTags-Klasse nicht in der Page-Klasse sehen.

Wenn der Fremdschlüssel in der PageTags-Datenbanktabelle auf Nullen zulassen gesetzt ist, erstellt Linq to SQL die Verknüpfung nicht, wenn Sie die Tabellen in den Designer ziehen, auch wenn Sie eine Beziehung auf dem SQL Server erstellt haben.

+0

Der Verband an seinem Platz ist, und ich kann das Kind Sammlung programmatisch zugreifen. Das Problem besteht darin, dass an der Sammlung vorgenommene Änderungen (insbesondere das Entfernen von Objekten) nicht in der Datenbank beibehalten werden. – AaronSieb

+0

Dumme Frage, aber machen Sie Änderungen? –

+0

Ja. Wenn ich die Änderungen festlege, erhalte ich einen doppelten Schlüsselfehler (weil die neuen Tags die alten Tags überlappen). – AaronSieb

0

Dies ist einer dieser Bereiche, in denen OR-Mapping haarig werden kann. Die Bereitstellung dieser TagString-Eigenschaft macht die Dinge etwas bequemer, aber auf lange Sicht verschleiert sie, was wirklich geschieht, wenn jemand die TagString-Eigenschaft verwendet. Durch das Verbergen der Tatsache, dass Ihre Datenänderungen durchgeführt werden, kann jemand sehr leicht mitkommen und den TagString setzen, ohne Ihre Page-Entität im Rahmen eines DataContext zu verwenden, was zu einigen schwer zu findenden Fehlern führen könnte.

Eine bessere Lösung wäre das Hinzufügen einer Tag-Eigenschaft für die Page-Klasse mit dem L2S-Modelldesigner und die direkte Bearbeitung der PageTags innerhalb der Gültigkeitsdauer eines DataContext für die Eigenschaft Tags. Lassen Sie die TagString-Eigenschaft nur lesen, damit sie geniert werden kann (und weiterhin einige Vorteile bietet), aber beseitigen Sie die Verwirrung und die Schwierigkeit beim Festlegen dieser Eigenschaft. Diese Art von Änderung klärt die Absicht und macht es offensichtlich, was passiert und was von den Verbrauchern des Page-Objekts benötigt wird, um dies zu erreichen.

Da Tags eine Eigenschaft Ihres Page-Objekts sind, werden alle Änderungen an dieser Sammlung in Reaktion auf Remove- oder Add-Aufrufe ordnungsgemäß in der Datenbank gelöscht oder eingefügt.

+0

Ich möchte das PageTag nicht sofort löschen, nachdem es aus der Sammlung entfernt wurde. Ich versuche, es zu entfernen, wenn der Eltern-DataContext SubmitChanges() aufruft. Die Aktualisierungssemantik der PageTagString-Eigenschaft soll sich nicht von den anderen Dateneigenschaften des Objekts unterscheiden. – AaronSieb

0

Aaron,

Anscheinend haben Sie durch Ihre PageTag Datensätze Schleife, ruft DeleteOnSubmit für jeden. Linq to SQL sollte eine Sammelabfrage erstellen, um alle Datensätze auf einmal zu löschen, wenn Sie SubmitChanges aufrufen, daher sollte der Overhead minimal sein.

PageTags.Clear(); 

mit

foreach (PageTag tag in PageTags) 
    myDataContext.DeleteOnSubmit(tag); 
+0

Wie würde ich über meine Page-Klasse auf den DataContext zugreifen? – AaronSieb

+0

Fügen Sie Ihrer partiellen Klasse PageTag ein DataContext-Mitglied hinzu. Teilklasse PageTag { DataClassesDataContext myDataContext = new DataClassesDataContext(); öffentliche Zeichenkette TagString { ..etc. –

0

Aaron ersetzen:

ein Datacontext Mitglied auf Ihre PageTag partielle Klasse hinzufügen.

partial class PageTag 
{ 
    DataClassesDataContext myDataContext = new DataClassesDataContext(); 

    public string TagString { 

..etc.

+0

Wäre das nicht ein unabhängiger DataContext vom übergeordneten DataContext, der zum Abrufen des PageTags verwendet wird? Ich glaube nicht, dass das Löschen passieren wird, wenn ich SubmitChanges für den * parent * DataContext aufruft. – AaronSieb

+0

Es wird funktionieren. Stellen Sie sich den DataContext als eine weitere Verbindung zur Datenbank vor. Der Grund, warum dies seltsam aussieht, liegt darin, dass Sie Ihre PageTag-Objekte aus architektonischer Sicht wahrscheinlich außerhalb der PageTag-Teilklasse und nicht innerhalb der PageTag-Teilklasse manipulieren sollten. –

+0

Ich erhalte die Ausnahme "Eine Entität, die nicht angehängt wurde, kann nicht entfernt werden." wenn ich versuche, sie aus der zweiten DataConnection zu entfernen. Dies sieht nicht so aus, als würde es die Anforderung erfüllen, Änderungen zu verschieben, bis der ursprüngliche DataContext übergeben wird. – AaronSieb

0

Größere Codebeispiel bei Robert Harvey Wunsch geschrieben:

DataContext.cs Datei:

namespace MyProject.Library.Model 
{ 
    using Tome.Library.Parsing; 
    using System.Text; 

    partial class Page 
    { 
     //Part of Robert Harvey's proposed solution. 
     MyDataContext mDataContext = new TomeDataContext(); 

     public string TagString { 
      get { 
       StringBuilder output = new StringBuilder(); 
       foreach (PageTag tag in PageTags) { 
        output.Append(tag.Tag + " "); 
       } 

       if (output.Length > 0) { 
        output.Remove(output.Length - 1, 1); 
       } 

       return output.ToString(); 
      } 
      set { 
       string[] tags = value.Split(' '); 
       //Original code, fails to mark for deletion. 
       //PageTags.Clear(); 

       //Robert Harvey's suggestion, thorws exception "Cannot remove an entity that has not been attached." 
       foreach (PageTag tag in PageTags) { 
        mDataContext.PageTags.DeleteOnSubmit(tag); 
       } 

       foreach (string tag in tags) { 
        PageTag PageTag = new PageTag(); 
        PageTag.Tag = tag; 
        PageTags.Add(PageTag); 
       } 
      } 
     } 

     private bool mIsNew; 
     public bool IsNew { 
      get { 
       return mIsNew; 
      } 
     } 

     partial void OnCreated() { 
      mIsNew = true; 
     } 

     partial void OnLoaded() { 
      mIsNew = false; 
     } 
    } 
} 

Repository Methoden:

public void Save() { 
    mDataContext.SubmitChanges(); 
} 

public Page GetPage(string pageName) { 
    Page page = 
     (from p in mDataContext.Pages 
     where p.FileName == pageName 
     select p).SingleOrDefault(); 

    return page; 
} 

Verbrauch:

[AcceptVerbs(HttpVerbs.Post)] 
public ActionResult Edit(string pageName, FormCollection formValues) { 
    Page updatedPage = mRepository.GetPage(pageName); 

    //TagString is a Form value, and is set via UpdateModel. 
    UpdateModel(updatedPage, formValues.ToValueProvider()); 
    updatedPage.FileName = pageName; 

    //At this point NO changes should have been written to the database. 

    mRepository.Save(); 

    //All changes should NOW be saved to the database. 

    return RedirectToAction("Index", "Pages", new { PageName = pageName }); 
} 
1

Sorry, mein Fehler. Das wird nicht funktionieren.

Es sieht wirklich so aus, als müssten Sie dies in Ihrem Repository tun und nicht in Ihrer Page-Klasse. Dort haben Sie Zugriff auf Ihren ursprünglichen Datenkontext.

Es gibt eine Möglichkeit, den ursprünglichen Datenkontext zu "anhängen", aber zu dem Zeitpunkt, an dem Sie das tun, ist es ziemlich zum Code-Geruch geworden.

+0

Vielen Dank für Ihre Eingabe. Es sieht so aus, als ob LINQ zu dieser Art von Struktur nicht in der Lage ist:/ – AaronSieb

Verwandte Themen