NHibernate: Resultate Transformieren mit DistinctRootEntityResultTransformer
Ich hatte vor einiger Zeit ein recht mühsames Problem: Obwohl ich Objekte nach deren Id (Primärschlüssel) aus der DB geholt habe sind diese mehrmals in meinem Resultat erschienen. Wie so oft war der Fehler eigentlich eine Kleinigkeit, doch solange man gar nicht auf die Idee kommt an der richtigen Stelle danach zu suchen steht man vor einem grossen Mysterium.
Ausgangslage
Für das stark vereinfachte Beispiel nutze ich die 3 Klassen Order, OrderItem und Product. Die Objekte dienen nur zum ablegen der Daten und verfügen über keine Geschäftslogik. Das Feld Id ist jeweils der Primärschlüssel der gleichnamigen Tabellen. OrderItem ist sowohl mit Product wie auch mit Order verbunden.
Ein Test schlägt fehl
Mit dem untenstehenden Test werden die nötigen Objekte angelegt und danach versucht die Order anhand der Id zu laden.
Das Resultat in diesem Test ist allerdings nicht wie erwartet eine 1, sondern eine 2. Schaut man sich das generierte SQL-Query an kann man auch erkennen was das Problem ist:
Hier wird nicht einfach nur ein SELECT gemacht, sondern das Resultat wird noch mit einem JOIN verknüpft. Meine Erwartung war das nur eine Zeile mit den Daten für das von mir gewünschte Objekt zurück geliefert wird. Durch den JOIN werden nun aber auch alle dazugehörigen OrderItems geladen:
Ursache
Der JOIN wurde von NHibernate nicht einfach aus lauter Freude gemacht. Die dafür nötige Anweisung stand so im Mapping:
Diese explizite Schreibweise hat den gleichen Effekt als würde man die Funktion zum holen der Daten so umschreiben:
Obwohl das Ergebnis gleich ist, sieht man so auf den ersten Blick das ein wenig mehr Daten kommen werden als man als Nutzer der Methode vermuten würde. (Klare Methodennamen wären wie so oft eine grosse Hilfe gewesen).
Lösung
Das Mapping durfte nicht verändert werden, da das so erzwungene Verhalten fürs gesamte Projekt gesehen Sinn machte. Auch war ein erzwungenes nicht laden der OrderItems für die weitere Verarbeitung ungünstig. Nach einigem Suchen wurde die Funktion schliesslich um eine Zeile erweitert:
DistinctRootEntityResultTransformer nimmt das Resultat der Abfrage und transformiert dieses wieder in die Root Entitäten. NHibernate packt auch ohne diese Zeile die OrderItems ins Order-Objekt, so aber merkt es dass es nur einen Order gibt und liefert entsprechend auch nur noch eines zurück.
Fazit
Wenn man mit OR-Mappern arbeitet sollte man bei der Entwicklung immer einen Blick auf die generierten Abfragen werfen. Meistens macht es das Richtige aber für den kleinen Spezialfall den man nun gerade braucht gibt es halt ab und zu ein klein wenig Nacharbeit. Was wieder mal zeigt: Trotz OR-Mappern sollte man als Entwickler doch ein wenig Ahnung von SQL haben.
Danksagung
Ich möchte hier noch Patrick Weibel danken. Ich durfte für den Blogpost seine Klasse PersistenceManager.cs aus dem ORM-Vortrag bei der .Net User Group Bern verwenden. Die Klasse zusammen mit den zahlreichen Mappings zum Nachschauen hat mir ermöglicht ein Minimal-Beispiel zusammen zu stellen, das man fürs selber experimentieren auf BitBucket herunterladen kann.