X

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.

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.

Liked this post? Empower your friends by sharing it!

View Comments (24)

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

  • 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.

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

  • 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.

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

  • 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.

  • 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.

  • Hi @Ian Rathbone:
    about performance is a huge topic to discuss, because in the most of cases It depends how query / expressions are written, so this is a first question a developer must ask by himself / herself..
    Responding to your question, Nhibernate demostrates an optimal framework, you can do many task following goods practices, and It's more flexible and mature of Entity framework, even EF Core..
    NH and EF use a first level cache system in order to retrive instances loaded previously, keeping their status in memory, this aproach evoids to load anytime whenever you need a persistent instance.
    If you want more performance, you need to consider bypassing this first level caching, this part is the most common situation when applications don't need to keep tracking of persistence instances.. in that case consider to use IStatelessSession.. and the trick is done!!.. if you're skilled with nhibernate.. the right choice could be Nhibernate with IStatelessSession.. you don't need to another Sql Translator like Dapper or MicroORM.. Nhibernate reachs the same performance with session stateless.

  • For multi-server environments we can also use second level cache that is shared between application instances.

  • Second cache must be the last option to improve performance, and not the main first thing to consider, because caching implies you must know well the TTL of cache data.. otherwise you retrieve deprecated data..

  • Yes, I agree. My suggestion is to consider second level caching as something that needs more planning and work than just configuring cache provider. Sites that doesn't produce much data (home pages etc) can actually benefit from second level cache as there's nothing time critical anyway in database. Still it's possible to get some load away from database this way.

  • Hi,
    very nice post!
    Does it works with a MySQL database on a Linux system too? Which provider do you suggest?
    Thank you.

  • Thanks!

    I just tried with one of my ASP.NET Core 3 projects that uses SQL Server. Application builds and runs well. I think it should work with MySQL too.

  • Hi @Joe:
    Why doesn't it work on another system like Linux ? It's trivial with NetCore !!!, you only need to use the right dialect SQL, so in your case MySql.. stop.
    Currently, Nhibernate supports many kinds of sql dialects, example Oracle, Sql Server (from 2000 to 2012, the last one doesn't mean It doesn't work with the last version of Sql Server..), MySql, PostgreSQL ecc.
    The only thing to do in your case is considering NetCore framework, use NHibernate 5.* version, and try it in your linux environment.

  • Hi Gunnar,

    Great article, thank you it helps alot. I have a question tho.

    Maybe i just lack experiences but i don't quite get how you were able to "abstract" the NH ISession with IMapperSession and still be able to have all the async query capabilities of NH ISession.

    In other words, how did you define the async query methods (or in that case the get property ) on Books in IMapperSession?

    Thx

  • Hi,

    These methods come with NHibernate. Just include NHibernate.Linq namespace and the methods are there. Similar to Entity Framework Core these methods are actually extension methods. From my IMapperSession I just have to give out IQueryable and extension methods make the magic.

  • Thank you Gunnar,

    I was hoping to keep NH out of my Domain project, but the cost of implementation seems quite high. I'll reference it as you said to get access to NH magic ^^.

  • Very happy to see they're still great people @ NHibernate environment.
    Good job Gunnar.

  • Just wanted to stop by a say thank you for writing this article. I've been using NHibernate for many but wanted to have a good example of using the async methods with asp.net core, your post made my day.

Related Post