Using LINQ to NHibernate

Since version 3.0 NHibernate has included a LINQ provider. In this recipe, we'll show you how to execute LINQ queries with NHibernate.

Getting ready

Complete the Getting Ready section given at the beginning of this chapter.

How to do it…

  1. Add a new folder named QueryByLinq to the project.
  2. Add a new class named 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();
        }
      }
    }
  3. Add a new 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);
        }
      }
    }
  4. Run the application.
  5. Start the QueryByLinq recipe.

How it works…

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.

There's more…

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.

See also

  • Eager loading with LINQ
  • Using custom functions in LINQ
  • Extending LINQ Provider
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.77.4