The Cloud Spanner NHibernate Dialect is now Generally Available

Introduction

Do you have an application that you developed with NHibernate and now you want to migrate to a cloud database? Google Cloud Spanner now supports the NHibernate Object Relational Mapper. You can reuse existing NHibernate code with the Cloud Spanner NHibernate dialect, or write new features while leveraging existing knowledge.The Cloud Spanner NHibernate dialect implements all the classes and interfaces that are needed to use Cloud Spanner with NHibernate. Add the Spanner NHibernate nuget package to your project and connect to Cloud Spanner as follows:

Installing

Install the Spanner NHibernate dialect:

dotnet add package Google.Cloud.Spanner.NHibernate --version 1.0.0

 

Connecting

Connect to Cloud Spanner with NHibernate:

using NHibernate;
using NHibernate.Cfg;

Configuration = new Configuration().DataBaseIntegration(db =>
{
    db.Dialect<SpannerDialect>();
    db.ConnectionString =
        "Data Source=projects/MY-PROJECT/instances/MY-INSTANCE/databases/MY-DATABASE";
});
var sessionFactory = Configuration.BuildSessionFactory();
using var session = configuration.SessionFactory.OpenSession();

var transaction = session.BeginTransaction();
var singer = new Singer
{
    FirstName = "Jamie",
    LastName = "Allison"
};
await session.SaveAsync(singer);
await transaction.CommitAsync();

You can also use a custom connection provider or a user supplied connection with Spanner NHibernate. See this example for more information.

Usage

The Cloud Spanner NHibernate dialect supports standard NHibernate features, like querying data using both the NHibernate Criteria API and Linq, managing associations and executing transactions. The dialect also supports specific Spanner features such as interleaved tables, mutations and stale reads.

Query with Linq

NHibernate 3.0 introduced the Linq to NHibernate provider. The Spanner NHibernate dialect implements translations for the most commonly used functions and operators so these can be used with LInq.

using var session = configuration.SessionFactory.OpenSession();

var singersBornBefore2000 = await session
    .Query<Singer>()
    .Where(s => s.BirthDate < new SpannerDate(2000, 1, 1))
    .OrderBy(s => s.BirthDate)
    .ToListAsync();
Console.WriteLine("Singers born before 2000:");
foreach (var singer in singersBornBefore2000)
{
    Console.WriteLine($"\t{singer.FullName}, born at {singer.BirthDate}");
}

var singersStartingWithAl = await session
    .Query<Singer>()
    .Where(s => s.FullName.StartsWith("Al"))
    .OrderBy(s => s.LastName)
    .ToListAsync();
Console.WriteLine("Singers with a name starting with 'Al':");
foreach (var singer in singersStartingWithAl)
{
    Console.WriteLine($"\t{singer.FullName}");
}

Interleaved Tables

Using interleaved tables for parent-child relationships in your schema can improve performance. Interleaving a child table with a parent means that the child records will be stored physically together with the parent. These relationships can be modeled and used as any other association in NHibernate.

using var session = configuration.SessionFactory.OpenSession();
using var transaction = session.BeginTransaction();

// Create a Singer, Album and Track.
// Album references Singer with a normal foreign key relationship.
var singer = new Singer
{
    FirstName = "Farhan",
    LastName = "Edwards",
};
var album = new Album
{
    Title = "Thinking Jam",
    Singer = singer,
};
// Track is interleaved in Album. This means that Track must use a composite primary
// key. In this example, the primary key is the Album ID and a Track number.
var track = new Track
{
    // A TrackIdentifier consists of the parent Album and a track number.
    TrackIdentifier = new TrackIdentifier(album, 1L),
    Title = "Always Sweet",
};
await session.SaveAsync(singer);
await session.SaveAsync(album);
await session.SaveAsync(track);
await transaction.CommitAsync();

This directory contains the complete mapping of all the entities that are used in the sample above.

Mutations

NHibernate will use Data Manipulation Language (DML) by default. You can instruct NHibernate to use mutations for all transactions that are executed using a specific NHibernate session, or for a single transaction.

var sessionFactory = configuration.Configuration.BuildSessionFactory();

// Create a session that will always use mutations.
using var session = sessionFactory
                        .OpenSession()
                        .SetBatchMutationUsage(MutationUsage.Always);

// Create a new Singer and save it. The insert will use a Mutation.
var transaction = sessionUsingMutations.BeginTransaction();
var singer = new Singer
{
    FirstName = "Wanda",
    LastName = "Yates",
};
await session.SaveAsync(singer);
await transaction.CommitAsync();

// You can also instruct a single transaction to use mutations.
using var session2 = sessionFactory.OpenSession();
var transaction2 = session.BeginTransaction(MutationUsage.Always);

Further Samples

The GitHub repository contains a directory with multiple samples for common use cases for working with NHibernate and/or Cloud Spanner.

Limitations

Cloud Spanner features that are not supported in NHibernate are listed here.

Getting Involved

We’d love to hear from you, especially if you’re a .NET developer considering Cloud Spanner or an existing Cloud Spanner customer who is considering using NHibernate for new projects. The project is open-source, and you can comment, report bugs, and open pull requests on Github.

 

By: Knut Olav Løite (Software Engineer)
Source: Google Cloud Blog

Previous Collegis Partners With Google Cloud To Unlock Value In Higher Education Data
Next Cybercrime Insurance Is Making The Ransomware Problem Worse