NHibernate on ASP.NET Core

NHibernate has been my favorite ORM for long time. Although it’s more complex for beginners than Entity Framework it’s more matured and many developers consider it to be practially an industry standard. NHibernate works with ASP.NET Core too. This blog post shows how to use NHibernate in ASP.NET Core MVC applications.

I was pleasantly suprised seeing that NHibernate is now on .NET Standard and it doesn’t come with any secret dependencies to full .NET Framework or some Windows-only library. I mean I can take ASP.NET Core project that uses NHibernate and just deploy it to Linux server. Bam! It works!

Getting started

First I created default ASP.NET Core application on Visual Studio and then added this NuGet package:

  • NHibernate (5.2.3)
  • System.Data.SqlClient (4.6.1) for ASP.NET Core 3.0

I defined simple Book class.

public class Book
{
    public virtual Guid Id { get; set; }
    public virtual string Title { get; set; }
}

Instead of XML definitions I went with class mappings and defined Book entity for NHibernate in code.

public class BookMap : ClassMapping<Book>
{
    public BookMap()
    {
        Id(x => x.Id, x =>
        {
            x.Generator(Generators.Guid);
            x.Type(NHibernateUtil.Guid);
            x.Column("Id");
            x.UnsavedValue(Guid.Empty);
        });

        Property(b => b.Title, x =>
        {
            x.Length(50);
            x.Type(NHibernateUtil.StringClob);
            x.NotNullable(true);
        });

        Table("Books");
    }
}

Same way we can define mappings for all other entities too.

Imitating DbContext with NHibernate

Although I’m not a big fan of Entity Framework I like the concept of DbContext. I decided to mimic it by introducing interface for DbContext replacement.

public interface IMapperSession
{
    void BeginTransaction();
    Task Commit();
    Task Rollback();
    void CloseTransaction();
    Task Save(Book entity);
    Task Delete(Book entity);

    IQueryable<Book> Books { get; }
}

It supports all important tasks with data, including trancactions. Why interface? Because I want to keep internals of implementation hidden.

Supporting multiple mappers. The same interface can be used also in projects where multiple mappers must be supported. It’s possible to use DbContext class that follows the same interface.

Here’s the implementation of my DbContext replacement.

public class NHibernateMapperSession : IMapperSession
{
    private readonly ISession _session;
    private ITransaction _transaction;

    public NHibernateMapperSession(ISession session)
    {
        _session = session;
    }

    public IQueryable<Book> Books => _session.Query<Book>();

    public void BeginTransaction()
    {
        _transaction = _session.BeginTransaction();
    }

    public async Task Commit()
    {
        await _transaction.CommitAsync();
    }

    public async Task Rollback()
    {
        await _transaction.RollbackAsync();
    }

    public void CloseTransaction()
    {
        if(_transaction != null)
        {
            _transaction.Dispose();
            _transaction = null;
        }
    }

    public async Task Save(Book entity)
    {
        await _session.SaveOrUpdateAsync(entity);
    }

    public async Task Delete(Book entity)
    {
        await _session.DeleteAsync(entity);
    }
}

On database side we have almost everything done except one thing – setting up and configuring ISession.

Registering NHibernate to dependency injection

I decided to go ASP.NET Core way and write nice extension method for service collection – AddNHibernate().

public static class NHibernateExtensions
{
    public static IServiceCollection AddNHibernate(this IServiceCollection services, string connectionString)
    {
        var mapper = new ModelMapper();
        mapper.AddMappings(typeof(NHibernateExtensions).Assembly.ExportedTypes);
        HbmMapping domainMapping = mapper.CompileMappingForAllExplicitlyAddedEntities();

        var configuration = new Configuration();
        configuration.DataBaseIntegration(c =>
        {
            c.Dialect<MsSql2012Dialect>();
            c.ConnectionString = connectionString;
            c.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
            c.SchemaAction = SchemaAutoAction.Validate;
            c.LogFormattedSql = true;
            c.LogSqlInConsole = true;
        });
        configuration.AddMapping(domainMapping);

        var sessionFactory = configuration.BuildSessionFactory();

        services.AddSingleton(sessionFactory);
        services.AddScoped(factory => sessionFactory.OpenSession());
        services.AddScoped<IMapperSession, NHibernateMapperSession>();

        return services;
    }
}

This method creates session factory, configures it and registers all required types to dependency injection.

Notice that session factory is registered as singleton but ISession and IMapperSession are registered to request scope.

In Startup class of web application we have to register NHibernate using the same method:

public void ConfigureServices(IServiceCollection services)
{
    var connStr = Configuration.GetConnectionString("DefaultConnection");

    services.AddNHibernate(connStr);
    services.AddControllersWithViews();
}

One thing here that bugs me a little bit it how I handle connection string. It’s possible to find some more intelligent approach but for demo purposes we can leave it like it is – it’s still nice and clean.

Injecting NHibernate to controllers

We can now inject NHibernate mapper session to controllers and start querying for data.

public class HomeController : Controller
{
    private readonly IMapperSession _session;

    public HomeController(IMapperSession session)
    {
        _session = session;
    }

    public IActionResult Index()
    {
        var books = _session.Books.ToList();

        return View(books);
    }
}

Or why not something more complex like here.

public IActionResult Index()
{
    var books = _session.Books
                        .Where(b => b.Title.StartsWith("How to"))
                        .ToList();

    return View(books);
}

Everything works now on basic level and it’s time to focus to more advanced topics.

Async methods

Similar to Entity Framework Core there are asynchronous methods available in NHibernate.

public async Task<IActionResult> Index()
{
    var books = await _session.Books
                              .Where(b => b.Title.StartsWith("How to"))
                              .ToListAsync();
    return View(books);
}

Here’s a little bit more advanced example.

public async Task<IActionResult> Index()
{
    var book = await _session.Books.FirstOrDefaultAsync();
    book.Title += " (sold out)";
    await _session.Save(book);

    var books = await _session.Books
                                .Where(b => b.Title.StartsWith("How to"))
                                .ToListAsync();
    return View(books);
}

As asynchronous methods are supported by ISession we have these also available through IMapperSession interface.

Transactions

We all know that there’s beginning and the end, alfa and omega. The popular pattern in Hibernate community is to use transactions and not relying on automatic transaction management.

Following community best practices we can write the code above like shown here.

try
{
    _session.BeginTransaction();

    var book = await _session.Books.FirstOrDefaultAsync();
    book.Title += " (sold out)";

    await _session.Save(book);
    await _session.Commit();
}
catch
{
    // log exception here
    await _session.Rollback();
}
finally
{
    _session.CloseTransaction();
}

It’s also possible to run select queries in transaction and I have seen some cases where this approach is actively used.

public async Task<IActionResult> Index()
{
    try
    {
        _session.BeginTransaction();
               
        var books = await _session.Books
                                    .Where(b => b.Title.StartsWith("How to"))
                                    .ToListAsync();
        await _session.Commit();

        var bookModels = _mapper.Map<BookModel>(books);
               
        return View(bookModels);
    }
    catch
    {
        // log error here
        await _session.Rollback();

        return View("Error");
    }
    finally
    {
        _session.CloseTransaction();
    }
}

One developer explained me using this model. If there are unexpected changes to objects then these changes are tracked by NHibernate and when session is to be disposed then changes are flushed. Users probably see no errors but these appear in log files. Making database queries in transactions only mean that all unsaved changes during flush are unwanted side effects and developers must solve these issues.

It probably leads to long and ugly controller actions or service layer methods but not necessarily. I came out with simple RunInTransaction() method for NHibernate mapper session.

public async Task RunInTransaction(Action action)
{
    try
    {
        BeginTransaction();

        action();

        await Commit();
    }
    catch
    {
        await Rollback();

        throw;
    }
    finally
    {
        CloseTransaction();
    }
}

This one is for cases when query returns data.

public async Task<T> RunInTransaction<T>(Func<Task<T>> func)
{
    try
    {
        BeginTransaction();

        var retval = await func();

        await Commit();

        return retval;
    }
    catch
    {
        await Rollback();

        throw;
    }
    finally
    {
        CloseTransaction();
    }
}

This is how we can rewrite books query in action using RunInTransaction().

public async Task<IActionResult> Index()
{
    try
    {
        var models = await _session.RunInTransaction(async () => 
        {
            var books = await _session.Books
                                      .Where(b => b.Title.StartsWith("How to"))
                                      .ToListAsync();
            return _mapper.Map<List<BookModel>>(books);
        });
              
        return View(models);
    }
    catch
    {
        // log error here

        return View("Error");
    }
}

If we use general error handling in web application then we can also leave out try-catch block.

public async Task<IActionResult> Index()
{
    var models = await _session.RunInTransaction(async () =>
    {
        var books = await _session.Books
                                  .Where(b => b.Title.StartsWith("How to"))
                                  .ToListAsync();
        return _mapper.Map<List<BookModel>>(books);
    });

    return View(models);
}

We can use similar code also in service layer classes.

Paging with NHibernate

Paging is popular topic and I have previously written few blog posts about it.

For paging I’m using current versions of my PagedResultBase (class given to UI pager components) and PagedResult<T> (used returning paged results).

public abstract class PagedResultBase
{
    public int CurrentPage { get; set; }

    public int PageCount { get; set; }

    public int PageSize { get; set; }

    public int RowCount { get; set; }
    public string LinkTemplate { get; set; }

    public int FirstRowOnPage
    {

        get { return (CurrentPage - 1) * PageSize + 1; }
    }

    public int LastRowOnPage
    {
        get { return Math.Min(CurrentPage * PageSize, RowCount); }
    }
}

public class PagedResult<T> : PagedResultBase
{
    public IList<T> Results { get; set; }

    public PagedResult()
    {
        Results = new List<T>();
    }
}

Here is my GetPagedAsync() extension method for NHibernate that returns paged results.

public static class NHibernatePagedResultExtensions
{
    public static async Task<PagedResult<T>> GetPagedAsync<T>(this IQueryable<T> query, int page, int pageSize)
    {
        var result = new PagedResult<T>
        {
            CurrentPage = page,
            PageSize = pageSize,
            RowCount = query.Count()
        };

        var pageCount = (double)result.RowCount / pageSize;
        result.PageCount = (int)Math.Ceiling(pageCount);

        var skip = (page - 1) * pageSize;
        result.Results = await query.Skip(skip).Take(pageSize).ToListAsync();

        return result;
    }
}

This is how we can use paging in controller action.

public async Task<IActionResult> Index(int page = 1)
{
    var result = await _session.RunInTransaction(async () =>
    {
        var books = await _session.Books
                                    .Where(b => b.Title.StartsWith("How to"))
                                    .GetPagedAsync(page, pageSize: 25);
        return _mapper.Map<PagedResult<BookModel>>(books);
    });

    return View(result);
}

Now let’s head to generated queries.

Clean SQL queries

One thing I don’t like about Entity Framework are messy queries. Let’s try to produce similar mess with NHibernate by setting up multiple where-clause trap. Entity Framework badly failed for me generating the ugliest SQL of century.

public async Task<IActionResult> Index()
{
    var books = await _session.Books
                              .Where(b => b.Title.StartsWith("B"))
                              .Where(b => b.Title.Length >= 5)
                              .Skip(1)
                              .Take(2)
                              .ToListAsync();
    return View();
}

Notice here two where-clauses at row. This is shortcut to common scenario where conditions are added to query dynamically based on given values. Want to scare yourself? Although different story but similar mess. Take a look at blog post What happens behind the scenes: NHibernate, Linq to SQL, Entity Framework scenario analysis by Oren Eini.

NHibernate has been far ahead of even Entity Framework 6 for years. We have to do something really stupid to mess things up in NHibernate to get such an ugly queries. This is how NHibernate solves the problem – just read between the lines literally.

NHibernate query translated to SQL

It’s minimal, nice and clean. No sub-select per where clause in LINQ expression tree. So, on .NET Core NHibernate still works the way it worked before and we don’t have to be afraid going with it.

Wrapping up

NHibernate has my warm welcome to .NET Core platform. It was and still is a leading ORM and stable work horse behind many complex data access layers in world. Over years NHibernate is modernized supporting now async calls and LINQ queries. It still remains kind of complex for beginners but with help of FluentNHibernate it will be easier to get started. Anyway I’m sure my next ASP.NET Core projects will go on NHibernate.

Gunnar Peipman

Gunnar Peipman is ASP.NET, Azure and SharePoint fan, Estonian Microsoft user group leader, blogger, conference speaker, teacher, and tech maniac. Since 2008 he is Microsoft MVP specialized on ASP.NET.

    10 thoughts on “NHibernate on ASP.NET Core

    • February 27, 2019 at 4:48 pm
      Permalink

      You dont need Fluent Nhibernate anymore, mapping by code is built in now.

    • February 28, 2019 at 11:39 pm
      Permalink

      Good to know. Thinking about switching… Tx!

    • March 29, 2019 at 7:47 pm
      Permalink

      Hi Gunnar, I’m new to NHibernate. Do you recommend any sources to start with? A book, a course …

    • April 1, 2019 at 6:51 am
      Permalink

      Hi, Fabricio. I learnt NHibernate by experimenting and what I found from internet. They have online documentation available and there are also many tutorials, articles and blog posts in internet that help you when getting started.

    • May 14, 2019 at 10:33 am
      Permalink

      People shoule be aware that Fluent NHibernate is absolete and that this is a good example on how not to do.

    • May 23, 2019 at 9:37 am
      Permalink

      Please forget FluentNhibernate, consider conformist API, It’s a modern way to map entities, then it’s integrated into nhibernate framework, you don’t have to add other dependencies like FluentNhibernate or others.

    • August 21, 2019 at 5:48 pm
      Permalink

      .Mappings(m => m.FluentMappings.AddFromAssembly(GetType().Assembly)) which kind of mapping should I use to map all my classesmap ?? Could you explain please?

    • September 5, 2019 at 3:58 pm
      Permalink

      Hey Gunnar – great article thank you!
      One question, do you know how the performance is with NHibernate these days?

      I only ask because we had to walk away from it a few years ago because there were massive performance issues and switching over to Dapper proved to solve a lot of those issues.

    • September 5, 2019 at 4:04 pm
      Permalink

      Hi!
      I have no complaints on NHibernate performance. There’s three querying API-s available with it. If one falls we can take another. Anyway you have to consider that full-blown mapper is more expensive resource-wise than Dapper and other micro-ORMs.

    Leave a Reply

    Your email address will not be published. Required fields are marked *