Get Even More Visitors To Your Blog, Upgrade To A Business Listing >>

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 : ClassMappingBook>
{
    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);

    IQueryableBook> 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 IQueryableBook> Books => _session.QueryBook>();

    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.DialectMsSql2012Dialect>();
            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.AddScopedIMapperSession, 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 TaskIActionResult> 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 TaskIActionResult> 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 TaskIActionResult> Index()
{
    try
    {
        _session.BeginTransaction();
               
        var books = await _session.Books
                                    .Where(b => b.Title.StartsWith("How to"))
                                    .ToListAsync();
        await _session.Commit();

        var bookModels = _mapper.MapBookModel>(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 TaskT> RunInTransactionT>(FuncTaskT>> 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 TaskIActionResult> Index()
{
    try
    {
        var models = await _session.RunInTransaction(async () => 
        {
            var books = await _session.Books
                                      .Where(b => b.Title.StartsWith("How to"))
                                      .ToListAsync();
            return _mapper.MapListBookModel>>(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 TaskIActionResult> Index()
{
    var models = await _session.RunInTransaction(async () =>
    {
        var books = await _session.Books
                                  .Where(b => b.Title.StartsWith("How to"))
                                  .ToListAsync();
        return _mapper.MapListBookModel>>(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.

  • Paging with Entity Framework Core
  • Returning paged results from repositories using PagedResult

For paging I’m using current versions of my PagedResultBase (class given to UI pager components) and PagedResult (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 PagedResultT> : PagedResultBase
{
    public IListT> Results { get; set; }

    public PagedResult()
    {
        Results = new ListT>();
    }
}

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

public static class NHibernatePagedResultExtensions
{
    public static async TaskPagedResultT>> GetPagedAsyncT>(this IQueryableT> query, int page, int pageSize)
    {
        var result = new PagedResultT>
        {
            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 TaskIActionResult> 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.MapPagedResultBookModel>>(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 TaskIActionResult> 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.

The post NHibernate on ASP.NET Core appeared first on Gunnar Peipman - Programming Blog.



This post first appeared on Gunnar Peipman - Programming, please read the originial post: here

Share the post

NHibernate on ASP.NET Core

×

Subscribe to Gunnar Peipman - Programming

Get updates delivered right to your inbox!

Thank you for your subscription

×