Since version 3.0 NHibernate has included a LINQ provider. In this recipe, we'll show you how to execute LINQ queries with NHibernate.
QueryByLinq
to the project.LinqQueries
to the folder:using System.Collections.Generic; using System.Linq; using NH4CookbookHelpers.Queries; using NH4CookbookHelpers.Queries.Model; using NHibernate; using NHibernate.Linq; namespace QueryRecipes.QueryByLinq { public class LinqQueries : IQueries { private readonly ISession _session; public LinqQueries(ISession session) { _session = session; } public IEnumerable<Movie> GetMoviesDirectedBy( string directorName) { return _session.Query<Movie>() .Where(x => x.Director == directorName) .ToList(); } public IEnumerable<Movie> GetMoviesWith( string actorName) { return _session.Query<Movie>() .Where(x => x.Actors.Any(ar => ar.Actor == actorName)) .ToList(); } public Book GetBookByISBN(string isbn) { return _session.Query<Book>() .FirstOrDefault(x => x.ISBN == isbn); } public IEnumerable<Product> GetProductsByPrice( decimal minPrice, decimal maxPrice) { return _session.Query<Product>() .Where(x => x.UnitPrice >= minPrice && x.UnitPrice <= maxPrice ) .OrderBy(x => x.UnitPrice) .ToList(); } } }
class
named Recipe
to the folder:using NH4CookbookHelpers; using NHibernate; namespace QueryRecipes.QueryByLinq { public class Recipe : QueryRecipe { protected override void Run(ISession session) { var queries = new LinqQueries(session); ShowQueryResults(queries); } } }
The entry point to LINQ querying in NHibernate is always the session.Query<T>()
extension method, which returns an IQueryable<T>
instance for further processing. If you want to retrieve all the entities of a certain type, you could use this instance directly:
foreach(var movie in session.Query<Movie>()) { //Do something with the movie }
The real power is revealed by adding criteria and other restrictions to the query, such as Where
and OrderBy
expressions.
As with any LINQ query, its execution is deferred, meaning that it will not be executed until the results are needed. Use the following code:
var query=session.Query<Movie>(); query = query.Where(x=>x.Name.StartsWith("Raiders ")); query = query.Where(x=>x.Name.EndsWith("the Lost Ark")); //No query will be executed until the next line runs. var movie = query.FirstOrDefault();
This means that you can add a criteria dynamically, based on application logic, and the query sent to the database will be a composition of all the added restrictions.
Let's go through the following four queries in the recipe:
GetMoviesDirectedBy
query:return _session.Query<Movie>() .Where(x => x.Director == directorName) .ToList();
This should be rather obvious. We are fetching the IQueryable<Movie>
and apply a Where
restriction to it. But why the ToList
call? Well, since the IQueries
interface specified that it expected an IEnumerable<Movie>
to be returned, we don't want to cause confusion and problems by returning the IQueryable<Movie>
(although it implements IEnumerable<Movie>
), which at that point has not yet been executed.
The following SQL query will be executed:
SELECT this_.Id as Id1_0_, this_.Name as Name1_0_, this_.Description as Descript4_1_0_, this_.UnitPrice as UnitPrice1_0_, this_.Director as Director1_0_ FROM Product this_ WHERE this_.ProductType = 'Movie' AND this_.Director = 'Steven Spielberg'
Note that an extra criterion was added to the query, in order to make sure that we only query on Movie entities.
GetMoviesWith
query:_session.Query<Movie>() .Where(x => x.Actors.Any(ar=>ar.Actor==actorName)) .ToList();
Again, we use the Movie
queryable, but this time we want the restriction to apply to the Actors
list on the Movie
entity. You could read the query as:
Return all movies where any of the actors' name is equal to the actorName
parameter.
The query executed looks something similar to this:
SELECT movie0_.Id as Id0_, movie0_.Name as Name0_, movie0_.Description as Descript4_0_, movie0_.UnitPrice as UnitPrice0_, movie0_.Director as Director0_ FROM Product movie0_ WHERE movie0_.ProductType='Movie' AND ( EXISTS ( SELECT actors1_.Id FROM ActorRole actors1_ WHERE movie0_.Id=actors1_.MovieId AND actors1_.Actor='Morgan Freeman' ) );
Here, the WHERE
clause contains an EXISTS
subquery on the ActorRole
table, restricted by the actor name we supplied, and the movie Id
from the outer query.
In the next few recipes, you will see the same queries getting implemented using a JOIN
instead of a subquery. Why didn't we do that here?
For starters, while LINQ provides a Join
method, its syntax quickly becomes a bit convoluted and it requires the collection entity (ActorRole
in this case) to expose a property that can be used to join the two entities. Also, unless we apply Distinct
to the query result, a join can potentially return duplicates, for example if Morgan Freeman played more than one role in a movie.
For reference, a LINQ Join
query would look similar to the following code:
_session.Query<Movie>() .Join( _session.Query<ActorRole>(), movie => movie, actorRole => actorRole.Movie, (movie, actorRole) => new { Movie = movie, ActorRole = actorRole }) .Where(ar => ar.ActorRole.Actor==actorName) .Select(ar => ar.Movie) .Distinct() .ToList();
GetBookByISBN
query:_session.Query<Book>() .FirstOrDefault(x => x.ISBN == isbn);
In this query, we're searching for a particular book by its ISBN. We could have used the Where method to specify the criterion, followed by an empty FirstOrDefault()
, but this terser format will result in exactly the same query.
Since FirstOrDefault
means "only the first result, return null if nothing is found", NHibernate can apply a limit to the number of rows it requests.
The resulting SQL Server query looks similar to this:
SELECT book0_.Id as Id0_, book0_.Name as Name0_, book0_.Description as Descript4_0_, book0_.UnitPrice as UnitPrice0_, book0_.ISBN as ISBN0_, book0_.Author as Author0_ FROM Product book0_ WHERE book0_.ProductType='Book' and book0_.ISBN='978-1-849513-04-3' ORDER BY CURRENT_TIMESTAMP OFFSET 0 ROWS FETCH FIRST 1 ROWS ONLY
NHibernate applies the limit using the recommended syntax of the configured database. In this case, an OFFSET
/FETCH
in the ORDER BY
clause is used and since we didn't specify a sort order in the query, CURRENT_TIMESTAMP
is used as a fallback. Other databases can use other constructs, such as SELECT TOP or LIMIT
.
If Single
or SingleOrDefault
had been used instead of FirstOrDefault
, an exception is expected if the query returns more than one row. In that scenario, no limit will be added to the query, potentially leading to the consumption of more resources.
GetProductByPrice
query:_session.Query<Product>() .Where(x => x.UnitPrice >= minPrice && x.UnitPrice <= maxPrice ) .OrderBy(x=>x.UnitPrice) .ToList();
Just like GetMoviesDirectedBy
, this query is very straightforward. The requested restriction is simply expressed in the Where
expression and we also order the results by UnitPrice
(ascending).
SQL Server executes the following query:
SELECT product0_.Id as Id0_, product0_.Name as Name0_, product0_.Description as Descript4_0_, product0_.UnitPrice as UnitPrice0_, product0_.ISBN as ISBN0_, product0_.Author as Author0_, product0_.Director as Director0_, product0_.ProductType as ProductT2_0_ FROM Product product0_ WHERE product0_.UnitPrice>=0 and product0_.UnitPrice<=15 ORDER BY product0_.UnitPrice ASC
Since the query was on the base class Product
, no restriction on the ProductType
column is present. Instead it is included in the SELECT
clause, together with all the properties required for both Movie
and Book
.
In this recipe, we let the Queries
class implement the interface IQueries
. That interface doesn't, yet, provide us with much, but we will use it in few of the upcoming recipes, to show that that we can provide the same results in many ways.
Under the hood, the NHibernate LINQ provider will transform the query expressions into Hibernate Query Language (HQL) syntax trees, which means that virtually everything that can be accomplished using HQL can also be done with LINQ. This may sound limiting, but in reality it rarely is. In addition, NHibernate provides ways to extend HQL with new functionality and this power can also be harnessed in LINQ. Read the recipe Extending LINQ Provider in Chapter 8, Extending NHibernate for further details.
3.147.77.4