2009-05-03 5 views
2

Ich habe eine Viele-zu-Viele-Beziehung zwischen Fotos und Tags: Ein Foto kann mehrere Tags haben und mehrere Fotos können dieselben Tags verwenden.Welcher Algorithmus für eine Viele-zu-Viele-Beziehung in NHibernate verwendet wird

Ich habe eine Schleife, die die Fotos in einem Verzeichnis scannt und dann NHibernate hinzufügt. Einige Tags werden zu den Fotos während dieses Prozesses hinzugefügt, z. Ein 2009-Tag, wenn das Foto im Jahr 2009 aufgenommen wurde.

Die Tag-Klasse implementiert Equals und GetHashCode und verwendet die Name-Eigenschaft als einzige Signatureigenschaft. Sowohl Photo als auch Tag haben Ersatzschlüssel und sind versioniert.

Ich habe einige Code ähnlich dem folgenden:

public void Import() { 
    ... 
    foreach (var fileName in fileNames) { 
     var photo = new Photo { FileName = fileName }; 
     AddDefaultTags(_session, photo, fileName); 
     _session.Save(photo); 
    } 
    ... 
} 

private void AddDefaultTags(…) { 
    ... 
    var tag =_session.CreateCriteria(typeof(Tag)) 
        .Add(Restriction.Eq(“Name”, year.ToString())) 
        .UniqueResult<Tag>(); 

    if (tag != null) { 
     photo.AddTag(tag); 
    } else { 
     var tag = new Tag { Name = year.ToString()) }; 
     _session.Save(tag); 
     photo.AddTag(tag); 
    } 
} 

Mein Problem ist, wenn der Tag nicht existiert, zum Beispiel das erste Foto eines neuen Jahres. Die AddDefaultTags-Methode prüft, ob das Tag in der Datenbank vorhanden ist, erstellt es und fügt es NHibernate hinzu. Das funktioniert hervorragend, wenn Sie ein einzelnes Foto hinzufügen, aber beim Importieren mehrerer Fotos im neuen Jahr und innerhalb derselben Arbeitseinheit schlägt es fehl, da es in der Datenbank noch nicht vorhanden ist und erneut hinzugefügt wird. Beim Abschließen der Arbeitseinheit schlägt sie fehl, da versucht wird, zwei Einträge in der Tabelle Tags mit demselben Namen hinzuzufügen ...

Meine Frage ist, wie Sie sicherstellen, dass NHibernate nur versucht, ein einzelnes Tag in der Datenbank zu erstellen die obige Situation. Muss ich eine Liste neu hinzugefügter Tags selbst verwalten oder kann ich das Mapping so einrichten, dass es funktioniert?

Antwort

2

Sie müssen _session.Flush() ausführen, wenn Ihre Kriterien nicht veraltete Daten zurückgeben sollten. Oder Sie sollten es richtig machen, indem Sie _session.FlushMode auf Auto stellen.

Mit FlushMode.Auto wird die Sitzung automatisch geleert, bevor die Kriterien ausgeführt werden.

EDIT: Und wichtig! Wenn Sie den Code lesen, den Sie angezeigt haben, sieht es nicht so aus, als würden Sie eine Transaktion für Ihre Arbeitseinheit verwenden. Ich würde empfehlen, Ihre Arbeitseinheit in einer Transaktion zu verpacken - das ist erforderlich, damit FlushMode.Auto funktioniert, wenn Sie NH2.0 + verwenden!

Weitere hier: NHibernate ISession Flush: Where and when to use it, and why?

+0

Es tut mir leid, dass ich diesen Teil nicht gut erklärt habe. Mein FlushMode ist auf Nie eingestellt. Ich arbeite in einer Arbeitseinheit, in der ich nach Abschluss einen expliziten Flush mache. Oh, und auch innerhalb einer Transaktion. – HakonB

+0

Oh. OK. Wenn Sie absolut gegen einen Aufruf der DB an der Stelle im Code sind, an der Sie ein neues Tag einfügen, glaube ich, dass es keine andere Möglichkeit gibt, als die Tags selbst zu verfolgen. Aber ich sehe nicht, warum Sie es nicht löschen würden, Sie können immer noch Ausnahmen in der Arbeitseinheit zurücksetzen. – asgerhallas

+0

Werfen Sie einen Blick hier, für die Rollback-Güte und Vorbehalte: http://objectissues.blogspot.com/2004/12/understanding-hibernate-transaction.html – asgerhallas

0

Wenn Sie möchten, dass das neue Tag in der Datenbank vorhanden ist, wenn Sie es jedes Mal überprüfen, wenn Sie die Transaktion nach dem Speichern festschreiben müssen, um es dort ablegen zu können.

Ein anderer Ansatz wäre, die Tags in einer Sammlung zu lesen, bevor Sie die Fotos verarbeiten. Dann würden Sie, wie Sie sagten, lokal suchen und nach Bedarf neue Tags hinzufügen. Wenn Sie mit dem Ordner fertig sind, können Sie die Sitzung festschreiben.

Sie sollten Ihre Mappings posten, da ich Ihre Frage möglicherweise nicht richtig interpretiert habe.

+0

Mein Problem mit diesem Ansatz ist, dass ich den Import als eine Arbeitseinheit ausführen und nichts an die Datenbank binden möchte, bevor die gesamte Einheit abgeschlossen ist. Im Idealfall sollte das Ausführen in einer Arbeitseinheit für die AddDefaultTags-Methode transparent sein, aber ich denke, das ist nicht möglich ... Ich denke, Sie verstehen mein Problem gut. Ich glaube nicht, dass die Mappings selbst ein Problem sind - es entscheidet vielmehr über dieses korrekte Muster, um die Mappings zu verwenden. – HakonB

0

Dies ist das typische „etwas sperren, die nicht da ist“ -Problem. Ich habe es schon einige Male gesehen und habe immer noch keine einfache Lösung dafür.

Das sind die Optionen, die ich weiß bis jetzt:

  • optimistisch: eine eindeutige Einschränkung auf den Namen und lassen Sie eine der Sitzungen verpflichten werfen auf. Dann versuchst du es nochmal. Sie müssen sicherstellen, dass Sie nicht in einer Endlosschleife enden, wenn ein anderer Fehler auftritt.
  • Pessimistisch: Wenn Sie ein neues Tag hinzufügen, sperren Sie die gesamte Tag-Tabelle mit TSQL.
  • .NET-Sperre: Sie synchronisieren die Threads mit .NET-Sperren. Dies funktioniert nur, wenn parallele Transaktionen im selben Prozess sind.
  • Stichworte erstellen eigene Sitzung (siehe unten) mit

Beispiel:

public static Tag CreateTag(string name) 
{ 
    try 
    { 
    using (ISession session = factors.CreateSession()) 
    { 
     session.BeginTransaction(); 
     Tag existingTag = session.CreateCriteria(typeof(Tag)) /* .... */ 
     if (existingtag != null) return existingTag; 
     { 
     session.Save(new Tag(name)); 
     } 
     session.Transaction.Commit(); 
    } 
    } 
    // catch the unique constraint exception you get 
    catch (WhatEverException ex) 
    { 
    // try again 
    return CreateTag(name); 
    } 
} 

Das sieht einfach, hat aber einige Probleme. Sie erhalten immer ein Tag, das entweder existiert oder erstellt (und sofort übergeben) wird. Das Tag, das Sie erhalten, stammt jedoch aus einer anderen Sitzung. Daher wird es für Ihre Hauptsitzung getrennt. Sie müssen es mit Kaskaden (die Sie wahrscheinlich nicht wollen) an Ihre Sitzung anhängen oder aktualisieren.

Das Erstellen von Tags ist nicht mehr an Ihre Haupttransaktion gekoppelt, dies war das Ziel, bedeutet aber auch, dass beim Zurücksetzen Ihrer Transaktion alle erstellten Tags in der Datenbank verbleiben. Mit anderen Worten: Das Erstellen von Tags ist nicht mehr Bestandteil Ihrer Transaktion.

Verwandte Themen