NHibernate Lambda Extensions (V1.0.0.0) - Documentation
1. Introduction
The .Net framework introduced LINQ, Lambda Expressions, and Extension Methods. In addition, an ORM (like NHibernate) can implement the IQueryable interface to allow transparent querying of a datasource using LINQ.
However, sometimes you want the expressiveness of a query language that is closer to the persistence implementation (i.e., a relational database) like NHibernate's Query Object the ICriteria interface. The trouble with the ICriteria interface is the use of 'magic strings' for property names which can hinder the use of some refactoring tools.
This project supplies Extension Methods and uses Lambda Expressions to provide some extra syntax to remove the 'magic strings' from your ICriteria queries.
So, for example:
.Add(Expression.Eq("Name", "Smith"))
becomes:
.Add<Person>(p => p.Name == "Smith")
With this kind of syntax there are no 'magic strings', and refactoring tools like 'Find All References', and 'Refactor->Rename' work perfectly.
It is worth mentioning that this project is intended to remove the references to 'magic strings' from the ICriteria API while maintaining it's opaqueness. It is not a LINQ provider for NHibernate. There are seperate projects already in place to handle this (considerably more complex) problem.
2. Getting started
Assuming you're already using NHibernate:
- Download and extract NhLambdaExtensions from here;
- Put the NHibernate.LambdaExtensions.dll next to your NHibernate.dll;
- Add a reference to NHibernate.LambdaExtensions;
- Add a
using NHibernate.LambdaExtensions;
statement.
That's it.
3. Simple expressions
Simple expressions (<, <=, ==, !=, > & >=) can be added using an Extension Method on the ICriteria interface that takes an appropriate Lambda Expression. e.g.,:
.Add<Person>(p => p.Name == "Smith")
C# allows another syntax that semantically identical to the above:
.Add((Person p) => p.Name == "Smith")
Note that the Lambda Expression is converted to a plain ICriterion at runtime. The Lambda Expression is not stored in the resulting ICriteria, so the only difference between:
.Add(Expression.Eq("Name", "Smith"))
and:
.Add<Person>(p => p.Name == "Smith")
is the compile time checking. At runtime this calls
.Add(Expression.Eq("Name", "Smith"))
in both cases.
All of the extensions on the ICriteria interface have corresponding extensions on the DetachedCriteria class. So you can write:
mySession
.CreateCriteria(typeof(Person))
.Add<Person>(p => p.Name == "Smith");
and also:
DetachedCriteria.For<Person>()
.Add<Person>(p => p.Name == "Smith");
4. SQL specific functions
Some SQL operators/functions do not have a direct equivalent in C#.
(e.g., the SQL where name like '%anna%'
).
Some LINQ providers might try to make a transparent mapping from the C#
Name.StartsWith("anna")
to the SQL where name like 'anna%'
.
The aim of this project is to provide a typesafe interface to ICriteria while
maintaining the opaqueness that makes it clearer to the developer what query
will be executed on the database. Instead of mapping the StartsWith
function, there is a corresponding SqlExpression class that allows strongly-typed
expressions to be created. So:
.Add(Expression.Like("Name", "%anna%"))
becomes:
.Add(SqlExpression.Like<Person>(p => p.Name, "%anna%"))
There is also a factory method SqlExpression.CriterionFor
to allow you to create arbitrary ICriterion for simple
expressions. This allows you to use simple expressions anywhere that an ICriterion
can be used (e.g., Expression.And
). So:
.Add<Person>(p => p.Name == "Smith")
can also be written as:
.Add(SqlExpression.CriterionFor<Person>(p => p.Name == "Smith"))
allowing expressions like:
.Add(Expression.Or(
SqlExpression.CriterionFor<Person>(p => p.Name == "test"),
SqlExpression.CriterionFor<Person>(p => p.Age > 5)));
5. Aliases
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
.CreateAlias("Father", "fatherAlias")
, the string "fatherAlias" does not correspond
to a property or class in the domain.
In NHibernate Lambda Extensions, aliases are assigned using an empty variable. The variable can be declared anywhere (but should be empty/default 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).
Aliases can be assigned using both the CreateCriteria
and the
CreateAlias
extensions:
Person fatherAlias = null;
mySession
.CreateCriteria(typeof(Person))
.CreateAlias<Person>(p => p.Father, () => fatherAlias)
.Add<Person>(p => p.Name == fatherAlias.Name);
To create an alias on the root of DetachedCriteria use:
Person personAlias = null;
DetachedCriteria<Person>.Create(() => personAlias)
See below for more examples.
6. Projections
Projections are made typesafe through the LambdaProjection
class,
with the Property
method returning an IProjection. In addition, the
SimpleProjection class has an extension to allow you to alias the projections. So:
.SetProjection(Projections.Property("Age").As("ageAlias"))
can be written as:
.SetProjection(LambdaProjection.Property<Person>(p => p.Age).As(() => ageAlias))
There are additional factory methods to create other projections (e.g., Max, Min, Avg, etc.) See below for more examples.
7. Subqueries
Subqueries are put together using the LambdaSubquery
class. Each of the
original NHibernate factory methods has an equivalent on LambdaSubquery, so:
.Add(Subqueries.PropertyIn("Name", myDetachedCriteriaSubquery))
becomes:
.Add(LambdaSubquery.Property<Person>(p => p.Name).In(myDetachedCriteriaSubquery))
In addition to these factory methods are the methods Where
, WhereAll
,
and WhereSome
along with an Extension Method on the DetachedCriteria
class, which allows the use of Lambda Expressions to describe simple comparisons. So:
.Add(Subqueries.PropertyGt("Age", myDetachedCriteriaSubquery))
becomes:
.Add(LambdaSubquery.Where<Person>(p => p.Age > myDetachedCriteriaSubquery.As<int>()))
8. Examples
Below are some examples of how to write ICriteria using the 'old' style, and then rewritten using NHibernate Lambda Extensions.
Criteria Examples
Create Criteria With Alias
Eq
Eq Alternative Syntax
Eq With Alias
Order
Order Using Alias
Create Criteria Association With Alias
Create Criteria Alias Association With Alias And Join Type
Create Alias
Create Alias Using Alias With Join Type
Set Fetch Mode
Set Lock Mode
Aliased Eq Property
DetachedCriteria Examples
Create With Alias
Create Criteria Association With Alias And Join Type
Create Alias
Create Alias From Alias
Set Fetch Mode
SqlExpression Examples
Between
Between Using Alias
Like
Is Null
Is Empty
In
Generic In
Not
And
Conjunction
LambdaProjection Examples
Property
Property Using Alias
Property Alias Using Fluent Interface
Aliased Property
Avg
Count
Group Property
LambdaSubquery Examples
Property Eq
Property Eq Alternative Syntax
Property Gt Using Alias
Property Gt Using Alias Alternative Syntax
Property Eq All
Property In
Criteria Examples
Create Criteria With Alias
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person), "personAlias");
Using NHibernate Lambda Extensions:
Person personAlias = null;
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person), () => personAlias);
Eq
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person))
.Add(Restrictions.Eq("Name", "test name"));
Using NHibernate Lambda Extensions:
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person))
.Add<Person>(p => p.Name == "test name");
Eq Alternative Syntax
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person))
.Add(Restrictions.Eq("Name", "test name"));
Using NHibernate Lambda Extensions:
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person))
.Add((Person p) => p.Name == "test name");
Eq With Alias
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person), "personAlias")
.Add(Restrictions.Eq("personAlias.Name", "test name"));
Using NHibernate Lambda Extensions:
Person personAlias = null;
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person), () => personAlias)
.Add(() => personAlias.Name == "test name");
Order
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person))
.AddOrder(Order.Desc("Name"));
Using NHibernate Lambda Extensions:
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person))
.AddOrder<Person>(p => p.Name, Order.Desc);
Order Using Alias
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person), "personAlias")
.AddOrder(Order.Asc("personAlias.Name"))
.AddOrder(Order.Desc("personAlias.Age"));
Using NHibernate Lambda Extensions:
Person personAlias = null;
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person), () => personAlias)
.AddOrder(() => personAlias.Name, Order.Asc)
.AddOrder(() => personAlias.Age, Order.Desc);
Create Criteria Association With Alias
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person))
.CreateCriteria("Children", "childAlias")
.Add(Restrictions.Eq("Nickname", "test"));
Using NHibernate Lambda Extensions:
Child childAlias = null;
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person))
.CreateCriteria((Person p) => p.Children, () => childAlias)
.Add<Child>(c => c.Nickname == "test");
Create Criteria Alias Association With Alias And Join Type
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person), "personAlias")
.CreateCriteria("personAlias.Children", "childAlias", JoinType.LeftOuterJoin)
.Add(Restrictions.Eq("Nickname", "test"));
Using NHibernate Lambda Extensions:
Person personAlias = null;
Child childAlias = null;
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person), () => personAlias)
.CreateCriteria(() => personAlias.Children, () => childAlias, JoinType.LeftOuterJoin)
.Add<Child>(c => c.Nickname == "test");
Create Alias
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person))
.CreateAlias("Father", "fatherAlias");
Using NHibernate Lambda Extensions:
Person fatherAlias = null;
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person))
.CreateAlias<Person>(p => p.Father, () => fatherAlias);
Create Alias Using Alias With Join Type
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person), "personAlias")
.CreateAlias("personAlias.Father", "fatherAlias", JoinType.LeftOuterJoin);
Using NHibernate Lambda Extensions:
Person personAlias = null;
Person fatherAlias = null;
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person), () => personAlias)
.CreateAlias(() => personAlias.Father, () => fatherAlias, JoinType.LeftOuterJoin);
Set Fetch Mode
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person))
.SetFetchMode("Father", FetchMode.Eager);
Using NHibernate Lambda Extensions:
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person))
.SetFetchMode<Person>(p => p.Father, FetchMode.Eager);
Set Lock Mode
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person))
.CreateAlias("Father", "fatherAlias")
.SetLockMode("fatherAlias", LockMode.Upgrade);
Using NHibernate Lambda Extensions:
Person fatherAlias = null;
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person))
.CreateAlias<Person>(p => p.Father, () => fatherAlias)
.SetLockMode(() => fatherAlias, LockMode.Upgrade);
Aliased Eq Property
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person))
.CreateAlias("Children", "childAlias")
.Add(Expression.EqProperty("Name", "childAlias.Nickname"));
Using NHibernate Lambda Extensions:
Child childAlias = null;
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person))
.CreateAlias<Person>(p => p.Children, () => childAlias)
.Add<Person>(p => p.Name == childAlias.Nickname);
DetachedCriteria Examples
Create With Alias
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>("personAlias");
Using NHibernate Lambda Extensions:
Person personAlias = null;
DetachedCriteria after =
DetachedCriteria<Person>.Create(() => personAlias);
Create Criteria Association With Alias And Join Type
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>()
.CreateCriteria("Children", "childAlias", JoinType.LeftOuterJoin)
.Add(Restrictions.Eq("Nickname", "test"));
Using NHibernate Lambda Extensions:
Child childAlias = null;
DetachedCriteria after =
DetachedCriteria.For<Person>()
.CreateCriteria((Person p) => p.Children, () => childAlias, JoinType.LeftOuterJoin)
.Add<Child>(c => c.Nickname == "test");
Create Alias
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>()
.CreateAlias("Father", "fatherAlias");
Using NHibernate Lambda Extensions:
Person fatherAlias = null;
DetachedCriteria after =
DetachedCriteria.For<Person>()
.CreateAlias<Person>(p => p.Father, () => fatherAlias);
Create Alias From Alias
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>("personAlias")
.CreateAlias("personAlias.Father", "fatherAlias");
Using NHibernate Lambda Extensions:
Person personAlias = null;
Person fatherAlias = null;
DetachedCriteria after =
DetachedCriteria<Person>.Create(() => personAlias)
.CreateAlias(() => personAlias.Father, () => fatherAlias);
Set Fetch Mode
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>()
.SetFetchMode("Father", FetchMode.Eager);
Using NHibernate Lambda Extensions:
DetachedCriteria after =
DetachedCriteria.For<Person>()
.SetFetchMode<Person>(p => p.Father, FetchMode.Eager);
SqlExpression Examples
Between
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>()
.Add(Restrictions.Between("Age", 5, 10));
Using NHibernate Lambda Extensions:
DetachedCriteria after =
DetachedCriteria.For<Person>()
.Add(SqlExpression.Between<Person>(p => p.Age, 5, 10));
Between Using Alias
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>("personAlias")
.Add(Restrictions.Between("personAlias.Age", 5, 10));
Using NHibernate Lambda Extensions:
Person personAlias = null;
DetachedCriteria after =
DetachedCriteria<Person>.Create(() => personAlias)
.Add(SqlExpression.Between(() => personAlias.Age, 5, 10));
Like
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>()
.Add(Restrictions.Like("Name", "%test%"));
Using NHibernate Lambda Extensions:
DetachedCriteria after =
DetachedCriteria.For<Person>()
.Add(SqlExpression.Like<Person>(p => p.Name, "%test%"));
Is Null
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>()
.Add(Restrictions.IsNull("Name"));
Using NHibernate Lambda Extensions:
DetachedCriteria after =
DetachedCriteria.For<Person>()
.Add(SqlExpression.IsNull<Person>(p => p.Name));
Is Empty
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>()
.Add(Restrictions.IsEmpty("Children"));
Using NHibernate Lambda Extensions:
DetachedCriteria after =
DetachedCriteria.For<Person>()
.Add(SqlExpression.IsEmpty<Person>(p => p.Children));
In
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>()
.Add(Restrictions.In("Name", new string[] { "name1", "name2", "name3" }));
Using NHibernate Lambda Extensions:
DetachedCriteria after =
DetachedCriteria.For<Person>()
.Add(SqlExpression.In<Person>(p => p.Name, new string[] { "name1", "name2", "name3" }));
Generic In
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>()
.Add(Restrictions.InG<int>("Age", new int[] { 1, 2, 3 }));
Using NHibernate Lambda Extensions:
DetachedCriteria after =
DetachedCriteria.For<Person>()
.Add(SqlExpression.In<Person, int>(p => p.Age, new int[] { 1, 2, 3 }));
Not
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>()
.Add(Restrictions.Not(Restrictions.Gt("Age", 5)));
Using NHibernate Lambda Extensions:
DetachedCriteria after =
DetachedCriteria.For<Person>()
.Add(SqlExpression.Not<Person>(p => p.Age > 5));
And
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>()
.Add(Restrictions.And(
Restrictions.Eq("Name", "test"),
Restrictions.Gt("Age", 5)));
Using NHibernate Lambda Extensions:
DetachedCriteria after =
DetachedCriteria.For<Person>()
.Add(Restrictions.And(
SqlExpression.CriterionFor<Person>(p => p.Name == "test"),
SqlExpression.CriterionFor<Person>(p => p.Age > 5)));
Conjunction
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>()
.Add(Restrictions.Conjunction()
.Add(Restrictions.Eq("Name", "test"))
.Add(Restrictions.Gt("Age", 5)));
Using NHibernate Lambda Extensions:
DetachedCriteria after =
DetachedCriteria.For<Person>()
.Add(Restrictions.Conjunction()
.Add<Person>(p => p.Name == "test")
.Add<Person>(p => p.Age > 5));
LambdaProjection Examples
Property
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>()
.SetProjection(Projections.Property("Name"));
Using NHibernate Lambda Extensions:
DetachedCriteria after =
DetachedCriteria.For<Person>()
.SetProjection(LambdaProjection.Property<Person>(p => p.Name));
Property Using Alias
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>()
.SetProjection(Projections.Alias(Projections.Property("Name"), "nameAlias"));
Using NHibernate Lambda Extensions:
string nameAlias = null;
DetachedCriteria after =
DetachedCriteria.For<Person>()
.SetProjection(LambdaProjection.Alias(LambdaProjection.Property<Person>(p => p.Name), () => nameAlias));
Property Alias Using Fluent Interface
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>()
.SetProjection(Projections.Property("Age").As("ageAlias"));
Using NHibernate Lambda Extensions:
int ageAlias = 0;
DetachedCriteria after =
DetachedCriteria.For<Person>()
.SetProjection(LambdaProjection.Property<Person>(p => p.Age).As(() => ageAlias));
Aliased Property
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>("personAlias")
.SetProjection(Projections.Property("personAlias.Age"));
Using NHibernate Lambda Extensions:
Person personAlias = null;
DetachedCriteria after =
DetachedCriteria<Person>.Create(() => personAlias)
.SetProjection(LambdaProjection.Property(() => personAlias.Age));
Avg
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>()
.SetProjection(Projections.Avg("Age"));
Using NHibernate Lambda Extensions:
DetachedCriteria after =
DetachedCriteria.For<Person>()
.SetProjection(LambdaProjection.Avg<Person>(p => p.Age));
Count
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>()
.SetProjection(Projections.Count("Age"));
Using NHibernate Lambda Extensions:
DetachedCriteria after =
DetachedCriteria.For<Person>()
.SetProjection(LambdaProjection.Count<Person>(p => p.Age));
Group Property
Using original ICriteria API:
DetachedCriteria before =
DetachedCriteria.For<Person>()
.SetProjection(Projections.GroupProperty("Name"));
Using NHibernate Lambda Extensions:
DetachedCriteria after =
DetachedCriteria.For<Person>()
.SetProjection(LambdaProjection.GroupProperty<Person>(p => p.Name));
LambdaSubquery Examples
Property Eq
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person))
.Add(Subqueries.PropertyEq("Name", DetachedCriteriaSubquery));
Using NHibernate Lambda Extensions:
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person))
.Add(LambdaSubquery.Property<Person>(p => p.Name).Eq(DetachedCriteriaSubquery));
Property Eq Alternative Syntax
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person))
.Add(Subqueries.PropertyEq("Name", DetachedCriteriaSubquery));
Using NHibernate Lambda Extensions:
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person))
.Add(LambdaSubquery.Where<Person>(p => p.Name == DetachedCriteriaSubquery.As<string>()));
Property Gt Using Alias
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person), "personAlias")
.Add(Subqueries.PropertyGt("personAlias.Age", DetachedCriteriaSubquery));
Using NHibernate Lambda Extensions:
Person personAlias = null;
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person), () => personAlias)
.Add(LambdaSubquery.Property(() => personAlias.Age).Gt(DetachedCriteriaSubquery));
Property Gt Using Alias Alternative Syntax
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person), "personAlias")
.Add(Subqueries.PropertyGt("personAlias.Age", DetachedCriteriaSubquery));
Using NHibernate Lambda Extensions:
Person personAlias = null;
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person), () => personAlias)
.Add(LambdaSubquery.Where(() => personAlias.Age > DetachedCriteriaSubquery.As<int>()));
Property Eq All
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person))
.Add(Subqueries.PropertyEqAll("Name", DetachedCriteriaSubquery));
Using NHibernate Lambda Extensions:
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person))
.Add(LambdaSubquery.WhereAll<Person>(p => p.Name == DetachedCriteriaSubquery.As<string>()));
Property In
Using original ICriteria API:
ICriteria before = CreateSession()
.CreateCriteria(typeof(Person))
.Add(Subqueries.PropertyIn("Name", DetachedCriteriaSubquery));
Using NHibernate Lambda Extensions:
ICriteria after = CreateSession()
.CreateCriteria(typeof(Person))
.Add(LambdaSubquery.Property<Person>(p => p.Name).In(DetachedCriteriaSubquery));