In the recipe Using calculated properties we learned how to use SQL expressions to provide custom calculations
for specific properties. Using a <subselect>
mapping we can to the same for an entire class.
MappingSubselects
to the MappingRecipes
project.PageHit
to the folder:using System; namespace MappingRecipes.MappingSubselects { public class PageHit { public virtual int Id { get; protected set; } public virtual string Url { get; set; } public virtual DateTime PageViewDateTime { get; set; } } }
PageStatisticsEntry
to the folder:namespace MappingRecipes.MappingSubselects { public class PageStatisticsEntry { public virtual string Url { get; protected set; } public virtual int ViewCount { get; protected set; } } }
Mappings.hbm.xml
to the folder:<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="MappingRecipes" namespace="MappingRecipes.MappingSubselects"> <class name="PageHit"> <id name="Id"> <generator class="native"/> </id> <property name="Url" not-null="true"/> </class> <class name="PageStatisticsEntry" mutable="false"> <subselect> SELECT Url, COUNT(*) as ViewCount FROM PageHit GROUP BY Url </subselect> <synchronize table="PageHit"/> <id name="Url"/> <property name="ViewCount"/> </class> </hibernate-mapping>
Recipe
to the folder:using System; using NH4CookbookHelpers; using NHibernate; namespace MappingRecipes.MappingSubselects { public class Recipe : HbmMappingRecipe { protected override void AddInitialData( ISessionFactory sessionFactory) { var random = new Random(); using (var session = sessionFactory .OpenStatelessSession()) { for (var i = 0; i < 100; i++) { session.Insert(new PageHit { Url = random.Next(10).ToString(), PageViewDateTime = DateTime.Now }); } } } public override void RunQueries(ISession session) { var stats = session.QueryOver<PageStatisticsEntry>() .Where(x => x.ViewCount > 2) .OrderBy(x => x.ViewCount).Desc.List(); foreach (var entry in stats) { Console.WriteLine("Url: {0}, View count: {1}", entry.Url, entry.ViewCount); } } } }
MappingSubselects
recipe.Subselect mappings are in a way the full class version of a formula mapping, but a better description is perhaps that it works similar to a SQL VIEW. It defines a SQL query which takes the place of a table, and all the returned columns can be queried, as if they were actual columns on a table.
In our recipe, we created a PageHit
table and mapping, and a <subselect>
mapping using that very same table. However, it may just as well refer to tables which have not been included in any other mappings:
<class name="PageStatisticsEntry" mutable="false"> <subselect> SELECT Url, COUNT(*) as ViewCount FROM PageHit GROUP BY Url </subselect> <synchronize table="PageHit"/> <id name="Url"/> <property name="ViewCount"/> </class>
The subselect
element is included in a regular class mapping, and takes the place of a table attribute. Instead, it defines the query that will be used as a table. One or more <synchronize>
elements can be also added. They inform NHibernate of which tables are used in the query, which is crucial if we add caching to our application. If something modifies the PageHit
table, any cached values for PageStatisticsEntry
will be invalidated in the cache.
We also added mutable="false"
to the mapping, to specify that we cannot update entities of this type. The resulting SQL would not make any sense.
18.118.144.248