Retrieving random content items (rows) from a SQL database in Orchard with HQL queries

by Oliver 22. February 2014 12:37

We're adding some Premium functionality to discoverize right now, and part of that is the so-called Premium block which is a showcase of six Premium entries. Now, choosing the right entries for that block is the interesting part: as long as we don't have six Premium entries to show, we want to fill up the left over space with some random entries that haven't booked our Premium feature, yet. Get random rows from SQL database There are plenty of articles and stackoverflow discussions on the topic of how to (quickly) retrieve some random rows from a SQL database. I wanted to get something to work simply and quickly, not necessarily high performance. Incorporating any kind of hand-crafted SQL query was really the last option since it would mean to get hold of an ISessionLocator instance to get at the underlying NHibernate ISession to then create a custom SQL query and execute it. Not my favorite path, really. Luckily, the IContentManager interface contains the method HqlQuery which returns an IHqlQuery containing these interesting details: /// <summary> /// Adds a join to a specific relationship. /// </summary> /// <param name="alias">An expression pointing to the joined relationship.</param> /// <param name="order">An order expression.</param> IHqlQuery OrderBy(Action<IAliasFactory> alias, Action<IHqlSortFactory> order); …and IHqlSortFactory contains a Random() method. This finally got me going! HQL queries in Orchard HQL queries are a great feature in (N)Hibernate that allow you to write almost-SQL queries against your domain models. I won't go into further detail here, but be sure to digest that! Orchard's IContentManager interface contains the method HqlQuery() to generate a new HQL query. Unfortunately, there's almost no usage of this feature throughout the whole Orchard solution. So let me document here how I used the HqlQuery to retrieve some random entries from our DB: // retrieve count items of type "Entry" sorted randomly return contentManager.HqlQuery()     .ForType("Entry")     .OrderBy(alias => alias.ContentItem(), sort => sort.Random())     .Slice(0, count)     .Select(item => item.Id); And one more: // retrieve <count> older items filtered by some restrictions, sorted randomly return contentManager.HqlQuery()     .ForPart<PremiumPart>()     .Where(alias => alias.ContentPartRecord<PremiumPartRecord>(),            expr => expr.Eq("Active", true))     .Where(alias => alias.ContentPartRecord<PremiumPartRecord>(),            expr => expr.Lt("BookingDateTime", recentDateTime))     .OrderBy(alias => alias.ContentItem(), sort => sort.Random())     .Slice(0, count)     .Select(item => item.Id); Even with the source code at hand, thanks to Orchard's MIT license, the implementation of this API in the over 600 lines long DefaultHqlQuery is not always straight-forward to put into practice. Most of all I was missing a unit test suite that would show off some of the core features of this API and I'm honestly scratching my head of how someone could build such an API without unit tests! Random() uses newid() : monitor the query performance The above solution was easy enough to implement once I've got my head around Orchard's HQL query API. But be aware that this method uses the newid() approach (more here) and thus needs to a) generate a new id for each row in the given table and b) sort all of those ids to then retrieve the top N rows. Orchard has this detail neatly abstracted away in the ISqlStatementProvider implementation classes. Here's the relevant code from SqlServerStatementProvider (identical code is used for SqlCe): public string GetStatement(string command) {     switch (command) {         case "random":             return "newid()";     }     return null; } For completeness, here's the generated SQL from the first query above (with variable names shortened for better readability): select content.Id as col_0_0_ from Test_ContentItemVersionRecord content     inner join Test_ContentItemRecord itemRec         on content.ContentItemRecord_id = itemRec.Id     inner join Test_ContentTypeRecord typeRec         on itemRec.ContentType_id = typeRec.Id where ( typeRec.Name in ('Entry') )     and content.Published = 1 order by newid() OFFSET 0 ROWS FETCH NEXT 3 ROWS ONLY This approach works well enough on small data sets but may become a problem if your data grows. So please keep a constant eye on all your random queries' performance. Happy HQL-ing!


by Oliver 17. August 2011 18:28

Lately, we encountered a problem with the speed of our search on for a certain set of search criteria. It sometimes used to take over a few seconds before the updated results were shown. This most likely seemed to be a problem with the database so I went to investigate the offending queries using the wonderful NHibernate Profiler. I found a very slow query: So I went and copied the long running query into a new query window in SSMS (SQL Server Management Studio). Since we use MS SQL Express on our production servers we don’t get the advanced database tuning advisor features of the full edition. Still, when you right click on any query window you’ll see the option “Include Actual Execution Plan” … [FULL]                                      [EXPRESS] … which already offers a lot of detail, as you can see in the following screenshot: When you right click on the execution plan you’ll see the option “Missing Index Details…” - there you get a CREATE INDEX statement that is ready to use once you give a name to the new index. I did this for three indexes and now we have this for the same query: 115 ms instead of 1993 ms – that’s an improvement of 94%! Even if DB queries lasting longer than 50-60 ms are not really fast anymore, we’ve still got quite an improvement here. Well, that’s it. Using a well-priced tool like NHibernate Profiler to identify slow queries and the Execution Plan feature of SSMS, we’re able to get quite a performance improvement in a short time. Happy Coding and Optimizing, Oliver

NHibernate Tooling

by robert 31. May 2010 15:08

Die Tools um NHiberante herum werden besser und mehr. Nachdem im letzten Jahr mit NHProf ein hervorragender Profiler erschien der uns schon manches mal sehr hilfreich war, kommt mit “Visual NHibernate” ein Code-Generator der sowohl von der Datenbankseite, als auch von der Modelseite her funktioniert. Aus meiner Sicht ist für uns ist NHProf jedoch zunächst wenig interessant, da wir zukünftig für neue Projekte mit Fluent-NHibnerate auf Mapping per Konvention setzen wollen und bestehende Projekte ja Ihre Mapping schon haben. Um bestehenden Legacy-Projekte den Wechsel auf ORM zu erleichtern, scheint Visual NHibernate bestens geeignet zu sein. Links: Visual NHibernate NHibernate Profiler (Robert)

NHibernate: Fetch Join + EntityCache

by Oliver 16. October 2009 13:28

In unserer Test-basierten Entwicklung hat sich die Verwendung von Setup-Klassen für unsere Entities etabliert. Unsere Tests sehen folgendem sehr ähnlich: .csharpcode, .csharpcode pre { font-size: small; color: black; font-family: Consolas, "Courier New", Courier, Monospace; background-color: #ffffff; /*white-space: pre;*/ } .csharpcode pre { margin: 0em; } .csharpcode .rem { color: #008000; } .csharpcode .kwrd { color: #0000ff; } .csharpcode .str { color: #006080; } .csharpcode .op { color: #0000c0; } .csharpcode .preproc { color: #cc6633; } .csharpcode .asp { background-color: #ffff00; } .csharpcode .html { color: #800000; } .csharpcode .attr { color: #ff0000; } .csharpcode .alt { background-color: #f4f4f4; width: 100%; margin: 0em; } .csharpcode .lnum { color: #606060; } [Test] public void FetchJoinTest() { var cat = _categorySetup.GetPersisted(); var pitch = _pitchSetup.GetPersisted(p => { p.Categories.Add(cat); return p; }); var reserv1 = _reservationSetup.GetPersisted(r => { r.Pitch = pitch; r.DateFrom = new DateTime(2009, 10, 31); r.DateTo = new DateTime(2009, 11, 13); return r; });   pitch = _pitchService.GetById(pitch.Id); Assert.That(pitch.Reservations.Count, EqualTo(2)); }   Leider funktioniert der Test so nicht - er schmeißt eine AssertionException, da pitch.Reservations keine Elemente enthält. Warum nicht??? Nach langem hin und her, bin ich mir endlich sicher, es verstanden zu haben: es liegt an NHibernate's Entity-Cache! Da das Pitch-Objekt ja zum Zeitpunkt der Abfrage   pitch = _pitchService.GetById(pitch.Id);   noch in der aktuellen Session geladen ist (was NHibernate meines Wissens nach anhand der ID feststellt), wird kein neues Objekt vom Typ Pitch instanziiert, sondern das schon existierende, pitch, zurückgegeben. Dieses enthielt aber noch keine Reservierungen zum Zeitpunkt der Persistierung - und so, wie es aussieht, werden diese auch nicht nachträglich geladen (für ein Objekt, das schon in der Session lebt). Die Lösung ist jetzt gar nicht mehr so schwer: wir schmeißen das vorhandene Objekt einfach weg! Dafür gibt es Session.Evict(): [Test] public void FetchJoinTest() { var cat = _categorySetup.GetPersisted(); var pitch = _pitchSetup.GetPersisted(p => { p.Categories.Add(cat); return p; }); var reserv1 = _reservationSetup.GetPersisted(r => { r.Pitch = pitch; r.DateFrom = new DateTime(2009, 10, 31); r.DateTo = new DateTime(2009, 11, 13); return r; });   _nHibernateHelper.Session.Flush(); _nHibernateHelper.Session.Evict(pitch);   pitch = _pitchService.GetById(pitch.Id); Assert.That(pitch.Reservations.Count, EqualTo(2)); } Jetzt läuft der Test mit Erfolg durch. Happy Coding!

"NHibernate.MappingException: Association references unmapped class" die Zweite

by Stefan 13. August 2009 11:26

Ich bin heute auf ebendiese Exception gestossen als ich folgende zeilen zu einem Mapping-File in CO hinzufuegte:  <set name="NHibernateFiles"> <key column="EntityId"/> <one-to-many class="SpeakFriend.Utilities.StoredFile, SpeakFriend.Utilities"/> </set> Per Google-Suche landete ich direkt auf unserem Blog und fand Olivers Beitrag. Ich dachte schon, das Problem waere erledigt. Einfach das Mapping-File auf "Embedded Ressource" setzen und fertig. Aber Pustekuchen, das war ja schon alles richtig eingestellt. Ich konnte auch StoredFile von CO aus persistieren . Ein Blick in die App.config half mit auch nicht weiter: <mapping assembly="SpeakFriend.Utilities" /> <mapping assembly="ConceptOffice.Core" /> <mapping assembly="ConceptOffice.Core.Client" /> Alles war fein referenziert. Und es klappte ja auch alles. Nur beim parsen des Mapping-Files hatte er ein Problem. Nun die Aufloesung: Ein Durchwuehlen des Stack-Traces fuehrte mich auf folgende Stelle:  protected NHibernateHelper _nhibernateHelperSqlLite = new NHibernateHelper(new Configuration().Configure("lite-nhibernate.cfg.xml") .BuildSessionFactory().OpenSession()); Beim Blick in lite-nhibernate.cfg.xml fiel es mir dann wie Schuppen von den Augen. Die fettgedruckte Zeile fehlte:  <mapping assembly="SpeakFriend.Utilities" /> <mapping assembly="ConceptOffice.Core" /> <mapping assembly="ConceptOffice.Core.Client" /> Fazit: Nur nicht aufgeben, man findet den Fehler immer irgendwann.

Cache + NHibernate.HibernateException: Illegal attempt to associate a collection with two open sessions + NUnit

by Oliver 7. April 2009 18:55

In unserem Projekt verwenden wir einen Cache zum Vorhalten von oft benutzten Daten, u.a. für Länder und Bundesländer. Für die Übersetzungen derselben in viele Sprachen habe ich einfach je eine einfache Tabelle angelegt mit einer Referenz auf das jeweilige (Bundes-)Land, dem Sprachen-Isocode und dem Namen in der entsprechenden Sprache. Per NHibernate-Mapping dann die Übersetzungen als <bag> an das (Bundes-)Land angeheftet - fertig. Beim NUnit-Testen bekam ich nach dem Einbau der Übersetzungen beim Update von (Bundes-)Ländern allerdings diesen Fehler: NHibernate.HibernateException: Illegal attempt to associate a collection with two open sessions. Offensichtlich waren die neuen Collections an den Domainobjekten Schuld. Was war passiert? Beim Aufrufen der Frontend-Seiten kam dieser Fehler nicht... Nun, die Exception gibt uns einen guten Tipp: Wir haben zwei offene Session, mit denen die Collection von Übersetzungen assoziiert werden soll. Aber warum nur in den Tests und warum dort überhaupt? Eine erste Erklärung: Im Frontend benutzen wir für die NHibernate-Sessions das One-Session-Per-Request-Pattern, so dass alle Manipulationen an den Session-Objekten (inkl. Update) innerhalb derselben Session passieren. Außerdem funktionierte dort das Dispose der Session zuverlässig. In unseren Tests war das ja leider nicht der Fall, so dass wir also irgendwie eine geöffnete Session nicht schließen. Durch die Benutzung eines Caches kamen wir in die Verlegenheit, Länderobjekte in einem Testcase in den Cache zu schreiben und diese in einem folgenden Testcase wiederzuverwenden. Als an dieser zweiten Stelle ein Update passieren sollte, knallte es, denn das Objekt im Cache hielt eine Referenz auf NHibernate-Collection, die noch mit einer vergangenen Session assoziiert war. Beim Update versucht NHibernate aber, dieselbe Collection an die aktuelle Session zu binden, was eine Exception wirft. Der Cache war also Schuld. Oder zumindest war das mein erster Gedanke. Und tatsächlich: Wenn ich im [SetUp] der NUnit-TestFixture ein Cache.Clear() aufrufe, verschwindet der Fehler! Keine Überraschung, denn jetzt benutzen wir ja keine Objekte aus "alten" Sessions mehr. Aber Moment mal, im Frontend benutzen wir doch denselben Cache?! Und da gibt es diesen Fehler nicht. Nach ein wenig Haareraufen, kam ich dann zurück zu der Feststellung, dass es ja ein Problem mit einer offenen Session gab. Also setzte ich da nochmal an. Für das Lifecycle-Management unserer Serviceklassen benutzen einen Autofac-IoC-Container. Dieser stellt auch die NHibernat-Session bereit. Zum Benutzen der Services haben wir eine ServiceLocator-Klasse, die uns die vom Container generierten Serviceinstanzen auf Anfrage zurückgibt. Um diese Architektur in den NUnit-Tests zu nutzen, schrieben wir flugs eine Basisklasse BaseTest, von der alle Testklassen ableiten und fortan Zugriff auf alle Services haben. Das Lifecycle-Management des Containers wird dort ebenfalls verwaltet.         public BaseTest()         {             InitializeContainer();         }         ~BaseTest()         {             DisposeContainer();         } Wie sich nun herausstellt, eine dumme Idee! Beim Debuggen stellte ich fest, dass der Konstruktor zwar von jeder TestFixture (also jeder Testklasse, die selbst mehrere Tests enthält) in Reihenfolge aufgerufen wird, dass aber der Destruktor einer Testklasse keineswegs zuverlässig vor dem Konstruktor der nächsten Testklasse ausgeführt wird! Damit wird der Container der ersten Testklasse nicht Dispose'd und in Folge die NHibernate-Session nicht geschlossen, so dass wir den o.g. Fehler bekommen. Wahrscheinlich ist das Ausführen von Businesslogik in einem NUnit-TestFixture-Konstruktor bzw. viel schlimmer im Destruktor ein absolutes NoNo. Dafür gibt es schließlich die Attribute [TestFixtureSetUp] und [TestFixtureTearDown]. Mit dem folgenden Code funktioniert denn auch alles wie gewollt:         [TestFixtureSetUp]         public virtual void TestFixtureSetUp()         {             InitializeContainer();         }         [TestFixtureTearDown]         public virtual void TestFixtureTearDown()         {             DisposeContainer();         } Ein kleiner Wehrmutstropfen bei dieser Lösung ist, dass man beim Testklassen-Schreiben jetzt daran denken muss, diese Methoden zu überschreiben, wenn man weitere Funktionalität in [TestFixtureSetUp] und/oder [TestFixtureTearDown] benötigt... Alles Gute, Oliver

NHibernate: spannende Fehlermeldung für ein NULL-DateTime

by Oliver 21. March 2009 22:13

DateTime-NULLs versteckte Fehlerquelle [More]

NHibernate: wundersame automatische Updates

by Oliver 27. February 2009 16:48

Kurze Suche, etwas überzogener Artikel (hier), aber die Kerninformation ist wichtig: Eigene Enumerationen sollten im Mapping-File nicht auf Int gemappt werden, denn ansonsten muss NHibernate die casten und das entsprechende Objekt wird "dirty", was zu dem wundersamen UPDATE führt. Bei uns z.B. in der Translation-Klasse der Fall: ... <property name="Type" column="Type" type="int" not-null="true" /> ... <property name="MetaType" column="MetaType" type="int" not-null="true" /> ...   Stattdessen also immer die Enumeration-Klasse angeben: ... <property name="Type" column="Type" type="TranslationType" not-null="true" /> ... <property name="MetaType" column="MetaType" type="MetaType" not-null="true" /> ... Jetzt gibt's keine ungewollten UPDATEs mehr. Schön.

NHibernate Criteria API zum Knutschen

by admin 25. September 2007 14:26

public RequestVisitedList GetBy(int userId, List<int> requestIds){ Disjunction disjunctionRequests = new Disjunction(); foreach (int requestId in requestIds) disjunctionRequests.Add( Expression.Eq("RequestId", requestId)); return new RequestVisitedList( _session.CreateCriteria(typeof(RequestVisited)) .Add(Expression.Eq("UserId", userId)) .Add(disjunctionRequests) .List<RequestVisited>() );} Der Code oben gibt eine Liste von allen Aufrufen einer Anfrage für eine bestimmten Benutzer zurück, eigentlich nichts Besonderes. Das großartige ist, das das Ganze in 3 min geschrieben war. Keine Stored Procedures, keine Strings zusammen bauen, einfach nur mit IntelliSense bewaffnet einen Abfrage Wunsch beschreiben und fertig ist das Ganze. Ein hübscher Nebeneffekt, der Code ist Datenbank unabhängig. Noch schöner wäre das Ganze wenn es für NHibernate schon einen LINQ Provider gäbe, denn soweit ich das Überblicke ist nur MS-SQL zurzeit voll unterstützt, alle anderen LINQ Provider stecken wohl noch in den Kinderschuhen.

((Research + Development) - Sleep) > 24

by admin 4. September 2007 04:53

Cooler Blog Titel, zu lesen gibt es den low frequency feed hier. Haupthema zur Zeit ist Linq für NHibernate.

About Oliver code blog logo I build web applications using ASP.NET and have a passion for javascript. Enjoy MVC 4 and Orchard CMS, and I do TDD whenever I can. I like clean code. Love to spend time with my wife and our children. My profile on Stack Exchange, a network of free, community-driven Q&A sites

About Anton code blog logo I'm a software developer at teamaton. I code in C# and work with MVC, Orchard, SpecFlow, Coypu and NHibernate. I enjoy beach volleyball, board games and Coke.