|
From: <ric...@us...> - 2010-11-02 07:02:24
|
Revision: 5256
http://nhibernate.svn.sourceforge.net/nhibernate/?rev=5256&view=rev
Author: ricbrown
Date: 2010-11-02 07:02:17 +0000 (Tue, 02 Nov 2010)
Log Message:
-----------
Added first-cut of QueryOver documentation.
Modified Paths:
--------------
trunk/nhibernate/doc/reference/master.xml
Added Paths:
-----------
trunk/nhibernate/doc/reference/modules/query_queryover.xml
Modified: trunk/nhibernate/doc/reference/master.xml
===================================================================
--- trunk/nhibernate/doc/reference/master.xml 2010-10-31 19:13:38 UTC (rev 5255)
+++ trunk/nhibernate/doc/reference/master.xml 2010-11-02 07:02:17 UTC (rev 5256)
@@ -16,6 +16,7 @@
<!ENTITY batch SYSTEM "modules/batch.xml">
<!ENTITY query-hql SYSTEM "modules/query_hql.xml">
<!ENTITY query-criteria SYSTEM "modules/query_criteria.xml">
+<!ENTITY query-queryover SYSTEM "modules/query_queryover.xml">
<!ENTITY query-sql SYSTEM "modules/query_sql.xml">
<!ENTITY filters SYSTEM "modules/filters.xml">
<!ENTITY performance SYSTEM "modules/performance.xml">
@@ -63,6 +64,7 @@
&batch;
&query-hql;
&query-criteria;
+ &query-queryover;
&query-sql;
&filters;
Added: trunk/nhibernate/doc/reference/modules/query_queryover.xml
===================================================================
--- trunk/nhibernate/doc/reference/modules/query_queryover.xml (rev 0)
+++ trunk/nhibernate/doc/reference/modules/query_queryover.xml 2010-11-02 07:02:17 UTC (rev 5256)
@@ -0,0 +1,361 @@
+<chapter id="queryqueryover">
+ <title>QueryOver Queries</title>
+
+ <para>
+ The ICriteria API
+ is NHibernate's implementation of Query Object.
+ NHibernate 3.0 introduces the QueryOver api, which combines the use of
+ Extension Methods
+ and
+ Lambda Expressions
+ (both new in .Net 3.5) to provide a statically typesafe wrapper round the ICriteria API.
+ </para>
+ <para>
+ QueryOver uses Lambda Expressions to provide some extra
+ syntax to remove the 'magic strings' from your ICriteria queries.
+ </para>
+ <para>
+ So, for example:
+ </para>
+ <programlisting><![CDATA[.Add(Expression.Eq("Name", "Smith"))]]></programlisting>
+ <para>becomes:</para>
+ <programlisting><![CDATA[.Where<Person>(p => p.Name == "Smith")]]></programlisting>
+ <para>
+ With this kind of syntax there are no 'magic strings', and refactoring tools like
+ 'Find All References', and 'Refactor->Rename' work perfectly.
+ </para>
+ <para>
+ Note: QueryOver is intended to remove the references to 'magic strings'
+ from the ICriteria API while maintaining it's opaqueness. It is <emphasis role="underline">not</emphasis> a LINQ provider;
+ NHibernate has a built-in Linq provider for this.
+ </para>
+
+ <sect1 id="queryqueryover-querystructure">
+ <title>Structure of a Query</title>
+
+ <para>
+ Queries are created from an ISession using the syntax:
+ </para>
+ <programlisting><![CDATA[IList<Cat> cats =
+ session.QueryOver<Cat>()
+ .Where(c => c.Name == "Max")
+ .List();]]></programlisting>
+ <para> </para>
+ <para>
+ Detached QueryOver (analagous to DetachedCriteria) can be created, and then used with an ISession using:
+ </para>
+ <programlisting><![CDATA[QueryOver<Cat> query =
+ QueryOver.Of<Cat>()
+ .Where(c => c.Name == "Paddy");
+
+IList<Cat> cats =
+ query.GetExecutableQueryOver(session)
+ .List();]]></programlisting>
+
+ <para>
+ Queries can be built up to use restrictions, projections, and ordering using
+ a fluent inline syntax:
+ </para>
+ <programlisting><![CDATA[var catNames =
+ session.QueryOver<Cat>()
+ .WhereRestrictionOn(c => c.Age).IsBetween(2).And(8)
+ .Select(c => c.Name)
+ .OrderBy(c => c.Name).Asc
+ .List<string>();]]></programlisting>
+
+ </sect1>
+
+ <sect1 id="queryqueryover-simpleexpressions">
+ <title>Simple Expressions</title>
+
+ <para>
+ The Restrictions class (used by ICriteria) has been extended to include overloads
+ that allow Lambda Expression syntax. The Where() method works for simple expressions (<, <=, ==, !=, >, >=)
+ so instead of:
+ </para>
+ <programlisting><![CDATA[ICriterion equalCriterion = Restrictions.Eq("Name", "Max")]]></programlisting>
+ <para>
+ You can write:
+ </para>
+ <programlisting><![CDATA[ICriterion equalCriterion = Restrictions.Where<Cat>(c => c.Name == "Max")]]></programlisting>
+ <para> </para>
+ <para>
+ Since the QueryOver class (and IQueryOver interface) is generic and knows the type of the query,
+ there is an inline syntax for restrictions that does not require the additional qualification
+ of class name. So you can also write:
+ </para>
+ <programlisting><![CDATA[var cats =
+ session.QueryOver<Cat>()
+ .Where(c => c.Name == "Max")
+ .And(c => c.Age > 4)
+ .List();]]></programlisting>
+ <para>
+ Note, the methods Where() and And() are semantically identical; the And() method is purely to allow
+ QueryOver to look similar to HQL/SQL.
+ </para>
+ <para> </para>
+ <para>
+ Boolean comparisons can be made directly instead of comparing to true/false:
+ </para>
+ <programlisting><![CDATA[ .Where(p => p.IsParent)
+ .And(p => !p.IsRetired)]]></programlisting>
+ <para> </para>
+ <para>
+ Simple expressions can also be combined using the || and && operators. So ICriteria like:
+ </para>
+ <programlisting><![CDATA[ .Add(Restrictions.And(
+ Restrictions.Eq("Name", "test name"),
+ Restrictions.Or(
+ Restrictions.Gt("Age", 21),
+ Restrictions.Eq("HasCar", true))))]]></programlisting>
+ <para>
+ Can be written in QueryOver as:
+ </para>
+ <programlisting><![CDATA[ .Where(p => p.Name == "test name" && (p.Age > 21 || p.HasCar))]]></programlisting>
+ <para> </para>
+ <para>
+ Each of the corresponding overloads in the QueryOver API allows the use of regular ICriterion
+ to allow access to private properties.
+ </para>
+ <programlisting><![CDATA[ .Where(Restrictions.Eq("Name", "Max"))]]></programlisting>
+ <para> </para>
+ <para>
+ It is worth noting that the QueryOver API is built on top of the ICriteria API. Internally the structures are the same, so at runtime
+ the statement below, and the statement above, are stored as exactly the same ICriterion. The actual Lambda Expression is not stored
+ in the query.
+ </para>
+ <programlisting><![CDATA[ .Where(c => c.Name == "Max")]]></programlisting>
+
+ </sect1>
+
+ <sect1 id="queryqueryover-additionalrestrictions">
+ <title>Additional Restrictions</title>
+
+ <para>
+ Some SQL operators/functions do not have a direct equivalent in C#.
+ (e.g., the SQL <literal>where name like '%anna%'</literal>).
+ These operators have overloads for QueryOver in the Restrictions class, so you can write:
+ </para>
+ <programlisting><![CDATA[ .Where(Restrictions.On<Cat>(c => c.Name).IsLike("%anna%"))]]></programlisting>
+ <para>
+ There is also an inline syntax to avoid the qualification of the type:
+ </para>
+ <programlisting><![CDATA[ .WhereRestrictionOn(c => c.Name).IsLike("%anna%")]]></programlisting>
+ <para> </para>
+ <para>
+ While simple expressions (see above) can be combined using the || and && operators, this is not possible with the other
+ restrictions. So this ICriteria:
+ </para>
+ <programlisting><![CDATA[ .Add(Restrictions.Or(
+ Restrictions.Gt("Age", 5)
+ Restrictions.In("Name", new string[] { "Max", "Paddy" })))]]></programlisting>
+ <para>
+ Would have to be written as:
+ </para>
+ <programlisting><![CDATA[ .Add(Restrictions.Or(
+ Restrictions.Where<Cat>(c => c.Age > 5)
+ Restrictions.On<Cat>(c => c.Name).IsIn(new string[] { "Max", "Paddy" })))]]></programlisting>
+
+ </sect1>
+
+ <sect1 id="queryqueryover-associations">
+ <title>Associations</title>
+
+ <para>
+ QueryOver can navigate association paths using JoinQueryOver() (analagous to ICriteria.CreateCriteria() to create sub-criteria).
+ </para>
+ <para>
+ The factory method QuerOver<T>() on ISession returns an IQueryOver<T>.
+ More accurately, it returns an IQueryOver<T,T> (which inherits from IQueryOver<T>).
+ </para>
+ <para>
+ An IQueryOver has two types of interest; the root type (the type of entity that the query returns),
+ and the type of the 'current' entity being queried. For example, the following query uses
+ a join to create a sub-QueryOver (analagous to creating sub-criteria in the ICriteria API):
+ </para>
+ <programlisting><![CDATA[IQueryOver<Cat,Kitten> catQuery =
+ session.QueryOver<Cat>()
+ .JoinQueryOver(c => c.Kittens)
+ .Where(k => k.Name == "Tiddles");]]></programlisting>
+ <para>
+ The JoinQueryOver returns a new instance of the IQueryOver than has its root at the Kittens collection.
+ The default type for restrictions is now Kitten (restricting on the name 'Tiddles' in the above example),
+ while calling .List() will return an IList<Cat>. The type IQueryOver<Cat,Kitten> inherits from IQueryOver<Cat>.
+ </para>
+ <para>
+ Note, the overload for JoinQueryOver takes an IEnumerable<T>, and the C# compiler infers the type from that.
+ If your collection type is not IEnumerable<T>, then you need to qualify the type of the sub-criteria:
+ </para>
+ <programlisting>IQueryOver<Cat,Kitten> catQuery =
+ session.QueryOver<Cat>()
+ .JoinQueryOver<<emphasis>Kitten</emphasis>>(c => c.Kittens)
+ .Where(k => k.Name == "Tiddles");</programlisting>
+ <para> </para>
+ <para>
+ The default join is an inner-join. Each of the additional join types can be specified using
+ the methods <code>.Inner, .Left, .Right,</code> or <code>.Full</code>.
+ For example, to left outer-join on Kittens use:
+ </para>
+ <programlisting><![CDATA[IQueryOver<Cat,Kitten> catQuery =
+ session.QueryOver<Cat>()
+ .Left.JoinQueryOver(c => c.Kittens)
+ .Where(k => k.Name == "Tiddles");]]></programlisting>
+
+ </sect1>
+
+ <sect1 id="queryqueryover-aliases">
+ <title>Aliases</title>
+
+ <para>
+ In the traditional ICriteria interface aliases are assigned using 'magic strings', however their value
+ does not correspond to a name in the object domain. For example, when an alias is assigned using
+ <code>.CreateAlias("Kitten", "kittenAlias")</code>, the string "kittenAlias" does not correspond
+ to a property or class in the domain.
+ </para>
+ <para>
+ In QueryOver, aliases are assigned using an empty variable.
+ The variable can be declared anywhere (but should
+ be <code>null</code> at runtime). The compiler can then check the syntax against the variable is
+ used correctly, but at runtime the variable is not evaluated (it's just used as a placeholder for
+ the alias).
+ </para>
+ <para>
+ Each Lambda Expression function in QueryOver has a corresponding overload to allow use of aliases,
+ and a .JoinAlias function to traverse associations using aliases without creating a sub-QueryOver.
+ </para>
+ <programlisting><![CDATA[Cat catAlias = null;
+Kitten kittenAlias = null;
+
+IQueryOver<Cat,Cat> catQuery =
+ session.QueryOver<Cat>(() => catAlias)
+ .JoinAlias(() => catAlias.Kittens, () => kittenAlias)
+ .Where(() => catAlias.Age > 5)
+ .And(() => kittenAlias.Name == "Tiddles");]]></programlisting>
+
+ </sect1>
+
+ <sect1 id="queryqueryover-projections">
+ <title>Projections</title>
+
+ <para>
+ Simple projections of the properties of the root type can be added using the <code>.Select</code> method
+ which can take multiple Lambda Expression arguments:
+ </para>
+ <programlisting><![CDATA[IList selection =
+ session.QueryOver<Cat>()
+ .Select(
+ c => c.Name,
+ c => c.Age)
+ .List<object[]>();]]></programlisting>
+ <para>
+ Because this query no longer returns a Cat, the return type must be explicitly specified.
+ If a single property is projected, the return type can be specified using:
+ </para>
+ <programlisting><![CDATA[IList<int> ages =
+ session.QueryOver<Cat>()
+ .Select(c => c.Age)
+ .List<int>();]]></programlisting>
+ <para>
+ However, if multiple properties are projected, then the returned list will contain
+ object arrays, as per a projection
+ in ICriteria. This could be fed into an anonymous type using:
+ </para>
+ <programlisting><![CDATA[var catDetails =
+ session.QueryOver<Cat>()
+ .Select(
+ c => c.Name,
+ c => c.Age)
+ .List<object[]>()
+ .Select(properties => new {
+ CatName = (string)properties[0],
+ CatAge = (int)properties[1],
+ });
+
+Console.WriteLine(catDetails[0].CatName);
+Console.WriteLine(catDetails[0].CatAge);]]></programlisting>
+ <para>
+ Note that the second <code>.Select</code> call in this example is an extension method on IEnumerable<T> supplied in System.Linq;
+ it is not part of NHibernate.
+ </para>
+ <para> </para>
+ <para>
+ QueryOver allows arbitrary IProjection to be added (allowing private properties to be projected). The Projections factory
+ class also has overloads to allow Lambda Expressions to be used:
+ </para>
+ <programlisting><![CDATA[IList selection =
+ session.QueryOver<Cat>()
+ .Select(Projections.ProjectionList()
+ .Add(Projections.Property<Cat>(c => c.Name))
+ .Add(Projections.Avg<Cat>(c => c.Age)))
+ .List<object[]>();]]></programlisting>
+ <para> </para>
+ <para>
+ In addition there is an inline syntax for creating projection lists that does not require the explicit class qualification:
+ </para>
+ <programlisting><![CDATA[IList selection =
+ session.QueryOver<Cat>()
+ .SelectList(list => list
+ .Select(c => c.Name)
+ .SelectAvg(c => c.Age))
+ .List<object[]>();]]></programlisting>
+ <para> </para>
+ <para>
+ Projections can also have arbitrary aliases assigned to them to allow result transformation.
+ If there is a CatSummary DTO class defined as:
+ </para>
+ <programlisting><![CDATA[public class CatSummary
+{
+ public string Name { get; set; }
+ public int AverageAge { get; set; }
+}]]></programlisting>
+ <para>
+ ... then aliased projections can be used with the AliasToBean<T> transformer:
+ </para>
+ <programlisting><![CDATA[CatSummary summaryDto = null;
+IList<CatSummary> catReport =
+ session.QueryOver<Cat>()
+ .SelectList(list => list
+ .SelectGroup(c => c.Name).WithAlias(() => summaryDto.Name)
+ .SelectAvg(c => c.Age).WithAlias(() => summaryDto.AverageAge))
+ .TransformUsing(Transformers.AliasToBean<CatSummary>())
+ .List<CatSummary>();]]></programlisting>
+
+ </sect1>
+
+ <sect1 id="queryqueryover-subqueries">
+ <title>Subqueries</title>
+
+ <para>
+ The Subqueries factory class has overloads to allow Lambda Expressions to express sub-query
+ restrictions. For example:
+ </para>
+ <programlisting><![CDATA[QueryOver<Cat> maximumAge =
+ QueryOver.Of<Cat>()
+ .SelectList(p => p.SelectMax(c => c.Age));
+
+IList<Cat> oldestCats =
+ session.QueryOver<Cat>()
+ .Where(Subqueries.WhereProperty<Cat>(c => c.Age).Eq(maximumAge))
+ .List();]]></programlisting>
+ <para> </para>
+ <para>
+ The inline syntax allows you to use subqueries without requalifying the type:
+ </para>
+ <programlisting><![CDATA[IList<Cat> oldestCats =
+ session.QueryOver<Cat>()
+ .WithSubquery.WhereProperty(c => c.Age).Eq(maximumAge)
+ .List();]]></programlisting>
+ <para> </para>
+ <para>
+ There is an extension method <code>As()</code> on (a detached) QueryOver that allows you to cast it to any type.
+ This is used in conjunction with the overloads <code>Where(), WhereAll(),</code> and <code>WhereSome()</code>
+ to allow use of the built-in C# operators for comparison, so the above query can be written as:
+ </para>
+ <programlisting><![CDATA[IList<Cat> oldestCats =
+ session.QueryOver<Cat>()
+ .WithSubquery.Where(c => c.Age == maximumAge.As<int>())
+ .List();]]></programlisting>
+
+ </sect1>
+
+</chapter>
\ No newline at end of file
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|