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:

  1. Download and extract NhLambdaExtensions from here;
  2. Put the NHibernate.LambdaExtensions.dll next to your NHibernate.dll;
  3. Add a reference to NHibernate.LambdaExtensions;
  4. 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));