Archive for category NHibernate

February Updates

I have a few quick announcements.

  • Fabio has been busy on some really amazing mapping magic.
  • Aaron Cure’s NHibernate 2.x Beginners Guide from Packt Publishing will be out in May.
  • I’m making progress on Project Nenverse. The concept is similar to Hibernate’s Envers. I’m not ready to announce it to the world just yet, but you want to look over the code or even contribute, it’s out there.
  • In an effort to avoid meta-blogging at all cost, I won’t mention the cool new stuff over on the right, or the new text up above.

That’s it for now.

Exploiting Context_Info for Fun and Audit

This is a continuation of my posts about auditing with triggers.

The Problem

My previous examples have used system_user as the source of user information in the audit log, which required you to impersonate users down to the database level, that is to open the connection in the context of the application user, not some shared generic account. There are several reasons why this is not a good idea:

  1. You either don’t have per-user credentials appropriate to create SQL server accounts (typical in public-facing web applications), or are unable to do so for various reasons ranging from security concerns to account management workload.
  2. When each user authenticates with their own credentials, this effectively disables connection pooling. Especially in high-volume web scenarios, this will kill performance and put a strain on server resources.
  3. I believe the loss of connection pooling will also burn through your license pool with per-user licensed SQL servers. I may be mistaken on this point, as I haven’t worked with a per-user licensed SQL server for the better part of a decade.
  4. If you are unwilling to use SQL server authentication, you will most likely have to set up Kerberos constrained delegation so that your database server trusts your web server to authenticate users. This is a royal PITA to implement.

The Trouble With Triggers

As far as input inside a trigger, you only have access to the data being manipulated, system functions, and very little else. You can’t use SELECT statements in triggers either, at least not against normal tables and views.

SQL Context Info

Starting with MS SQL Server 2000, you can associate up to 128 bytes of data with the current connection using the SET CONTEXT_INFO command. Starting with SQL 2005, you can use the CONTEXT_INFO function to retrieve this data. This data is available everywhere, including triggers.

If you use connection pooling (and you probably are), this data is not reset when the connection is reused.

To set your context info data, use the SET CONTEXT_INFO command like so:

DECLARE @Ctx varbinary(128)
SELECT @Ctx = CONVERT(varbinary(128), 'My context data goes here.')
SET CONTEXT_INFO @Ctx

SET CONTEXT_INFO can only be used with variables and constants, meaning you can’t CONVERT data types or concatenate strings or any other calculation in the same command.

To retrieve the context data, use the CONTEXT_INFO function:

DECLARE @CtxData varchar(128)
SELECT @CtxData = CONVERT(VarChar(128), CONTEXT_INFO())
PRINT @CtxData

This will output ‘My context data goes here.\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0’ etc. Note – The \0 is ASCII character 0 (NULL). Why? We’re converting the string to fixed-length binary and then converting it back.

Know Your Roots

image There was a time when programmers weren’t protected from the fact that strings are just chunks of memory. Just like programmers in the old days, you have two options: Pascal strings or C strings. You can stuff a length byte at the beginning of your context as in Pascal, or you can search for the null termination of your string as in the C language.

Also, because nchar and nvarchar are UNICODE UCS-2 strings, they use two bytes (of your 128 maximum) per character. char and varchar only use one byte per character, but the character set is limited. It’s a trade-off.

How does this help me?

We can use context info data to get information from our application in to our audit triggers. For example, we can get the current user name from our application or evem some identifying information about the location in our application, such as the name of the controller and action that triggered the event. Just be conscious of the 128 byte limitation. It’s probably better to store an id for more information such as the id of the user account, or an id that references some lookup table with application locations.

The Code, Sir.

We will override the GetConntion() method of DriverConnectionProvider so that every NHibernate connection will have the current username stored in the context.

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate.Connection;
using System.Data;

namespace NHibernate.Connection
{
    public class ContextDriverConnectionProvider : DriverConnectionProvider
    {

        public override System.Data.IDbConnection GetConnection()
        {
            var conn = base.GetConnection();
            SetContext(conn);
            return conn;
        }

        private const string CONTEXT_SQL = "declare @Length tinyint\ndeclare @Ctx varbinary(128)\nselect @Length = len(@username)\nselect @Ctx = convert(binary(1), @Length) + convert(varbinary(127), @username)\nset context_info @Ctx";

        protected virtual void SetContext(IDbConnection conn)
        {
            IDbCommand cmd = conn.CreateCommand();
            IDbDataParameter param = cmd.CreateParameter();
            cmd.CommandType = CommandType.Text;
            cmd.CommandText = CONTEXT_SQL;

            param.ParameterName = "@username";
            param.DbType = DbType.AnsiString;
            param.Size = 127;
            param.Value = System.Environmnt.UserName;
            cmd.Parameters.Add(param);

            cmd.ExecuteNonQuery();
        }

    }
}

This will run the following SQL code when NHibernate opens a SQL connection. Note that @username is a variable defined as a parameter on our IDbCommand. Also, I chose to use Pascal strings with the length stored in the first byte.

declare @Length tinyint
declare @Ctx varbinary(128)
select @Length = len(@username)
select @Ctx = convert(binary(1), @Length) + convert(varbinary(127), @username)
set context_info @Ctx

We’ll alter our trigger code to get the username from the context instead of system_user.

set nocount on
declare @Username varchar(127)
declare @Length tinyint
declare @Ctx varbinary(128)
select @Ctx = CONTEXT_INFO()
select @Length = convert(tinyint, substring(@Ctx, 1, 1))
select @Username = convert(varchar(127), substring(@Ctx, 2, 1 + @Length))
if (@Username is null) select @Username = SYSTEM_USER

Security Implications

I am not a security expert. I am certainly not a SQL security expert. However, I believe this method is secure for most scenarios. In my opinion, this is at least as secure as auditing through NHibernate interceptors and events.

Here’s a few ways to circumvent the system, and how you can prevent that from happening:

  • Manipulate the audit logs directly. Obviously, in any audit scenario, you should set permission on your audit tables to make them select and insert only.
  • Alter the context_info data directly. A user overwrites their own context_info with that of another user, effectively blaming their actions on someone else. This can be done through a SQL injection attack some other security breach. If you go on vacation with the front door open, don’t be surprised when your house is robbed.
  • Fail to set context_info. Again, this requires some other security breach. You may also change your trigger code to ROLLBACK TRANSACTION and RAISERROR when context_info is not set. This will force a rollback of the transaction (or in the case of no explicit transaction, the SQL statement that caused the trigger), abort the remainder of the SQL batch, and report an error. The approach requires everyone, including your DBA, to set an appropriate context_info before altering any audited data.

This is certainly my worst blog post this year, but don’t worry. I can do worse.

- Jason

Generate Audit Triggers from NHibernate v2

In my December 23rd post, I showed you how to generate audit tables and triggers from your NHibernate mappings. Since then, I had an amazing conversation with Fabio Maulo, NHibernate project owner, where among other things, he showed me a much better way to accomplish the same task.

In my last post, we used the NHibernate mapping as source data to create our script by hand. With this version, we’re going to take it a step further. When you add an IAuxilaryDatabaseObject to the mappings, it will be included in the ddl output of SchemaExport. These have no effect outside of SchemaExport. It’s a simple way to include additional items in your database setup scripts.

In our main program, we set up some additional columns to hold our audit data – user, timestamp, and action. These columns will be added to each audit table. Then, we use TriggerStrategy to add auxiliary database objects to our configuration.

        static void AddAuditing(Configuration cfg)
        {
            var columns = new List<auditcolumn>();
            columns.Add(new AuditColumn() {
                Name = "AuditUser",
                SqlType = "sysname",
                IsNullable = false,
                ValueFunction = delegate(TriggerActions actions)
                {
                    return "system_user";
                }
            });

            columns.Add(new AuditColumn()
            {
                Name = "AuditTimestamp",
                Value = new SimpleValue() { TypeName = NHibernate.NHibernateUtil.DateTime.Name },
                IsNullable = false,
                IncludeInPrimaryKey = true,
                ValueFunction = delegate(TriggerActions actions)
                {
                    return "getdate()";
                }
            });

            columns.Add(new AuditColumn()
            {
                Name = "AuditOperation",
                Value = new SimpleValue() { TypeName = NHibernate.NHibernateUtil.AnsiChar.Name },
                Length = 1,
                IsNullable = false,
                ValueFunction = delegate(TriggerActions actions)
                {
                    switch (actions)
                    {
                        case TriggerActions.Insert:
                            return "'I'";
                        case TriggerActions.Update:
                            return "'U'";
                        case TriggerActions.Delete:
                            return "'D'";
                        default:
                            throw new ApplicationException("Triggers handling multiple event types (INSERT, UPDATE, DELETE) are unsupported. ");
                    }
                }
            });

            new NHibernate.Audit.TriggerStrategy(
                new NHibernate.Audit.AuditTableBuilder(),
                new NHibernate.Audit.MsSqlTriggerBuilder(),
                columns).Configure(cfg);
	}

Trigger strategy simply hands off the work of audit table builder and trigger builder.

Audit table builder creates an audit table object for each table already in your mapping. It is also responsible for calculating the name of each audit table. Yes, the naming should probably be pulled in to a separate class, but oh well.

Audit table uses NHibernate’s table object internally to create a duplicate of each table, plus the additional audit columns, then exposes the drop/create scripts for this table object as an IAuxilaryDatabaseObject. I’ve ommitted some of the name-related static below.

using System.Collections.Generic;
using NHibernate.Mapping;

namespace NHibernate.Mapping
{

    public class AuditTable : AbstractAuxiliaryDatabaseObject
    {

        public AuditTable(string Name, Table DataTable, IEnumerable<AuditColumn> AuditColumns)
        {
            dataTable = DataTable;
            auditTable = new Table();
            auditColumns = AuditColumns;
            BuildAuditTable(Name);
        }

        protected readonly Table dataTable;
        protected readonly Table auditTable;
        protected readonly IEnumerable<AuditColumn> auditColumns;

        protected virtual void BuildAuditTable(string Name)
        {
            auditTable.Catalog = dataTable.Catalog;
            auditTable.Schema = dataTable.GetQuotedSchema();
            auditTable.Name = Name;

            foreach (Column column in dataTable.ColumnIterator)
                CopyColumn(column);

            CopyPrimaryKey(dataTable);

            AddAuditColumns();
        }

        protected virtual void CopyColumn(Column column)
        {
            auditTable.AddColumn((Column)column.Clone());
        }

        protected virtual void CopyPrimaryKey(Table dataTable)
        {
            if (dataTable.PrimaryKey != null)
            {
                var pk = new PrimaryKey();
                auditTable.PrimaryKey = pk;
                foreach (Column c in dataTable.PrimaryKey.ColumnIterator)
                {
                    Column auditTableColumn = auditTable.ColumnIterator.WithName(c.Name);
                    pk.AddColumn(auditTableColumn);
                }
            }
            else
            {
                throw new System.NotSupportedException("Tables without primary keys are not supported.");
            }
        }

        protected virtual void AddAuditColumns()
        {
            foreach (AuditColumn source in auditColumns)
            {
                Column dest = (Column)source.Clone();
                auditTable.AddColumn(dest);
                if (source.IncludeInPrimaryKey)
                    auditTable.PrimaryKey.AddColumn(dest);
            }
        }

        public override string SqlCreateString(NHibernate.Dialect.Dialect dialect, NHibernate.Engine.IMapping p, string defaultCatalog, string defaultSchema)
        {
            return auditTable.SqlCreateString(dialect, p, defaultCatalog, defaultSchema);
        }

        public override string SqlDropString(NHibernate.Dialect.Dialect dialect, string defaultCatalog, string defaultSchema)
        {
            return auditTable.SqlDropString(dialect, defaultCatalog, defaultSchema);
        }
    }

}

That handles our audit tables. Now let’s look at the triggers.

Abstract trigger builder builds insert, update, and delete triggers for each table. You should override BuildTriggerBody to customize the body of your trigger. I’ve created MsSqlTriggerBuilder to output my preferred trigger code.

NHibernate dialects have no concept of triggers and lack the required strings to build a DBMS-specific CREATE TRIGGER statements. Instead, you specify a trigger type inherited from AbstractTrigger specific to your database to build the CREATE TRIGGER statement. I’ve included MsSqlTrigger.

 

Triggers

 

That’s all folks. You can grab the code from my SVN repository at http://basiclyeverything.googlecode.com/svn/trunk/AuditLogExample/

Since I’m still using the system_user function to get the current user, you will still need to impersonate the user all the way down to the database level. I’ve found a way around this, and I will show it to you next time. Until then, Happy New Year.

Generate Audit Triggers From NHibernate

Last week, Fabio was showing me the Parallel Models pattern and explaining some of the work he’s doing with it. It is a VERY powerful concept.

What I’m going to show you today is the poor-mans version. It certainly doesn’t qualify as parallel model, but it’s pretty good audit logging if you meet the prerequisites.

The Problem

You need some audit logs for your current application, but you’re behind. In fact, you started the project already a month behind schedule. This needs to be quick and reliable. It’d be great if it didn’t intrude on the application, because you’ve got enough rattling around in your head already.

It sounds like you want SQL trigger audit logging. By the way, let’s say you’re using MS SQL Server. Let’s also say your application impersonates the user all the way down to the database level. That means your connection string says integrated security=SSPI or your dba enjoys the extra work of maintaining a sql login for each user. Those are prerequisites for this approach.

What’s a trigger?

A trigger is a special kind of stored procedure that automatically executes when an event occurs in the database server.
- CREATE TRIGGER, MSDN

If tables are the data structures in your database and stored procedures are the methods, then triggers are the event handlers. We’re concerned with three specific events – INSERT, UPDATE, and DELETE. We want to log each time those events happen to rows in our tables.

Trigger auditing has been around since at least the 1990s – since before SQL Server was even worth a damn. That’s MSSQL 6.5 folks. It’s sturdy and well-proven, although poorly implemented triggers will bring your server to a screeching halt. Fair warning – mine may be one of them.

We want to know what the data was before the change, what the data is now, who changed it, and when.

When you insert records in a table, the insert trigger for that table executes. The same goes for update and delete.

Triggers have two imaginary tables to help us with this task – inserted and deleted. Inside a trigger, they mostly work like real tables. Inserted always has the new data. Deleted always has the old data (from before the change.) So, in the case of an INSERT trigger, deleted is empty. For a DELETE trigger, inserted is always empty, since there is no new data with a deletion. For an UPDATE trigger, they both have data.

How does this help us? Well, suppose we have want to audit a table called Person. Let’s make a table called Audit_Person with the exact same columns as Person. When we delete data from Person, our DELETE trigger on Person will run. Inside our trigger, the deleted table has all of the rows that were just deleted from Person. We can copy that data to our audit table so we have a record of it by doing INSERT INTO Audit_Person SELECT * FROM deleted. Since the columns match up exactly, it works.

Wait a minute though. We need to know who deleted that data and when. Oh, and we need some way to indicate the different events.

Lucky for us, there’s some easy functions in SQL. system_user will give us the login of the current user – either a sql account name or a windows account name. Of course, GetDate() will tell us the current time and we’ll just write that to the table.

Let’s go ahead and write out a full trigger.

CREATE TRIGGER Person_onInsert ON Person FOR INSERT AS
SET NOCOUNT ON
INSERT INTO Audit_Person SELECT *, system_user, getdate(), 'I' FROM Inserted

Oh yeah, we need to add some fields in Audit_Person to hold the extra stuff. system_user is a custom sql type called sysname. It’s really nvarchar(256). Of course, getdate() is a datetime. Finally, we’re passing a capital letter I (for Inserted) as the last field, so we’ll need to make that a char(1) type.

The SET NOCOUNT ON command is there to suppress the extra (X rows affected) messages that SQL spits out. You’ll know what I’m talking about if you’ve ever clicked on the Messages tab after running a query.

So, that’s the audit logging. Now we just need to create an audit table and 3 triggers for each table in your application.

What does that get us?

Suppose we set up the audit table and all 3 triggers, then ran these 5 SQL commands:

INSERT INTO [Person] VALUES (NEWID(), 'Jason 1')
INSERT INTO [Person] VALUES (NEWID(), 'Jason 2')
INSERT INTO [Person] VALUES (NEWID(), 'Jason 3')
UPDATE Person SET Name = 'Jason 4'
DELETE FROM Person 

Our Audit_Person table would have these rows: (click the picture for 100% size)

image

 

Being Lazy

You’ve got 200 tables? Well, you’d better get to typing then.

OK. Just like you can generate a SQL script using NHibernate.Tool.hbm2ddl.SchemaExport, we can generate a script to build our audit tables and triggers from the mappings.

Now, we’re not going to parse out the XML in the hbm files. We’re going to read the proposed table schema – what SchemaExport will give you – directly from the NHibernate configuration object. This is the same way SchemaExport does it’s magic.

Here’s the code:

using System.IO;
using NHibernate.Cfg;
using NHibernate.Mapping;
using NHibernate.AdoNet.Util;

namespace AuditLogExample
{

    public class AuditScripter
    {

        public const string USER_DEFAULT = "system_user";
        public const string TIMESTAMP_DEFAULT = "getdate()";

        private enum Operations
        {
            Insert,
            Update,
            Delete
        }

        public AuditScripter(Configuration Configuration)
        {
            cfg = Configuration;
            mapping = cfg.BuildMapping();
            dialect = NHibernate.Dialect.Dialect.GetDialect(cfg.Properties);
            formatter = (NHibernate.Util.PropertiesHelper.GetBoolean(NHibernate.Cfg.Environment.FormatSql, cfg.Properties, true) ? FormatStyle.Ddl : FormatStyle.None).Formatter;
        }

        private readonly NHibernate.Dialect.Dialect dialect;
        private readonly Configuration cfg;
        private readonly NHibernate.Engine.IMapping mapping;
        private readonly IFormatter formatter;

        public void Execute(System.IO.TextWriter script)
        {
            var mappings = cfg.CreateMappings(dialect);
            ScriptObjectsForTables(mappings, script);
        }

        private void ScriptObjectsForTables(Mappings mappings, TextWriter script)
        {
            var tables = mappings.IterateTables;
            var auditTableBuilder = new AuditTableBuilder();
            foreach (Table table in tables)
            {
                if (table.IsPhysicalTable)
                {
                    var auditTable = auditTableBuilder.BuildAuditTable(table);
                    ScriptObjectsForTable(table, auditTable, script);
                }
            }
        }

        private void ScriptObjectsForTable(Table dataTable, Table auditTable, TextWriter script)
        {
            ScriptAuditTable(auditTable, script);
            ScriptInsertTrigger(dataTable, auditTable, script);
            ScriptUpdateTrigger(dataTable, auditTable, script);
            ScriptDeleteTrigger(dataTable, auditTable, script);
        }

        private void ScriptAuditTable(Table auditTable, TextWriter script)
        {
            string defaultCatalog = NHibernate.Util.PropertiesHelper.GetString(NHibernate.Cfg.Environment.DefaultCatalog, cfg.Properties, null);
            string defaultSchema = NHibernate.Util.PropertiesHelper.GetString(NHibernate.Cfg.Environment.DefaultSchema, cfg.Properties, null);

            string sqlCreateString = auditTable.SqlCreateString(dialect, mapping, defaultCatalog, defaultSchema);

            script.WriteLine(formatter.Format(sqlCreateString));

            var comments = auditTable.SqlCommentStrings(dialect, defaultCatalog, defaultSchema);

            foreach (string line in comments)
                script.Write(formatter.Format(line));

            script.WriteLine(formatter.Format("go")); // create trigger statements must in their own batch
        }

        private void ScriptInsertTrigger(Table dataTable, Table auditTable, TextWriter script)
        {
            ScriptTrigger(string.Format("{0}_onInsert", dataTable.Name),
                "insert", "'I'", "inserted",
                dataTable, auditTable, script);
        }

        private void ScriptUpdateTrigger(Table dataTable, Table auditTable, TextWriter script)
        {
            ScriptTrigger(string.Format("{0}_onUpdate", dataTable.Name),
                "update", "'U'", "inserted",
                dataTable, auditTable, script);
        }

        private void ScriptDeleteTrigger(Table dataTable, Table auditTable, TextWriter script)
        {
            ScriptTrigger(string.Format("{0}_onDelete", dataTable.Name),
                "delete", "'D'", "deleted",
                dataTable, auditTable, script);
        }

        /// <summary>
        /// Scripts out a create trigger sql statement
        /// </summary>
        /// <param name="triggerName">The name of the trigger</param>
        /// <param name="triggerOperation">insert, update, or delete</param>
        /// <param name="auditOperationValue">Value to insert for AuditOperation field</param>
        /// <param name="triggerTable">The "built-in" trigger table with affected data - inserted or deleted</param>
        /// <param name="dataTable"></param>
        /// <param name="auditTable"></param>
        /// <param name="script"></param>
        private void ScriptTrigger(string triggerName, string triggerOperation,
            string auditOperationValue, string triggerTable,
            Table dataTable, Table auditTable, TextWriter script)
        {

            Table trigger; // Just to calculate the name of the trigger
            trigger = new Table(triggerName);
            trigger.Schema = dataTable.Schema;
            trigger.Catalog = dataTable.Catalog;
            trigger.IsQuoted = dataTable.IsQuoted;

            string dataTableName = dataTable.GetQualifiedName(dialect);
            string auditTableName = auditTable.GetQualifiedName(dialect);
            triggerName = trigger.GetQualifiedName(dialect);

            string triggerHeading;
            string triggerBody;

            triggerHeading = string.Format("create trigger {0} on {1} for {2} as", triggerName, dataTableName, triggerOperation);
            triggerBody = string.Format("insert into {0} select *, {1}, {2}, {3} from {4}", auditTableName, TIMESTAMP_DEFAULT, USER_DEFAULT, auditOperationValue, triggerTable);

            script.Write(formatter.Format(triggerHeading));
            script.Write(formatter.Format("set nocount on"));
            script.Write(formatter.Format(triggerBody));
            script.WriteLine(formatter.Format("go")); // create trigger statements must in their own batch

        }

    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate.Mapping;

namespace AuditLogExample
{
    public class AuditTableBuilder
    {

        public Table BuildAuditTable(Table dataTable)
        {
            Table auditTable = new Table(string.Format("Audit_{0}",dataTable.Name));
            auditTable.Catalog = dataTable.Catalog;
            auditTable.Schema = dataTable.Schema;
            auditTable.IsQuoted = dataTable.IsQuoted;

            foreach(Column column in dataTable.ColumnIterator)
                CopyColumn(column, auditTable);

            CopyPrimaryKey(dataTable, auditTable);
            AddAuditColumns(auditTable);

            return auditTable;
        }

        private void CopyColumn(Column sourceColumn, Table auditTable)
        {
            Column newColumn = (Column)sourceColumn.Clone();
            auditTable.AddColumn(newColumn);
        }

        private void CopyPrimaryKey(Table dataTable, Table auditTable)
        {
            var pk = new PrimaryKey();
            foreach (Column sourceColumn in dataTable.PrimaryKey.ColumnIterator)
            {
                pk.AddColumn(auditTable.Column(sourceColumn.Name));
            }
            auditTable.PrimaryKey = pk;
        }

        private void AddAuditColumns(Table auditTable)
        {

            Column ts = new Column("AuditTimestamp");
            ts.SqlType = "datetime";
            ts.IsNullable = false;
            ts.DefaultValue = AuditScripter.TIMESTAMP_DEFAULT;
            auditTable.AddColumn(ts);
            auditTable.PrimaryKey.AddColumn(ts);

            Column user = new Column("AuditUser");
            user.Length = 256; // system_user returns a sysname, which is really nvarchar(256)
            user.SqlType = "sysname";
            user.IsNullable = false;
            user.DefaultValue = AuditScripter.USER_DEFAULT;
            auditTable.AddColumn(user);
            auditTable.PrimaryKey.AddColumn(user);

            Column op = new Column("AuditOperation");
            op.SqlType = "char";
            op.Length = 1;
            op.IsNullable = false;
            auditTable.AddColumn(op);

        }

    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NHibernate.Mapping;

namespace NHibernate.Mapping
{
    public static class TableExtensions
    {

        public static Column Column(this Table Table, string ColumnName)
        {
            return Table.ColumnIterator.Where(c => c.Name == ColumnName).FirstOrDefault();
        }

    }
}

I have a couple of gotchas and details to explain.

First, this works for me. I’m sure there are edge cases where everything goes horribly wrong. Please let me know when you find them.

Next, CREATE TRIGGER commands are VERY anti-social. They refuse to share a batch with any other statements. That’s why there’s a “go” between everything. In query analyzer (or the SSMS query window now), it starts a fresh batch.

If you know your SQL, this should look pretty simple. Pass an NHibernate configuration to the constructor of AuditScripter, then call Execute. The code loops through all of the physical tables in your configuration, first building a nearly-identical audit table, then creating the three triggers.

The primary key of the audit table has all the fields from the primary key of your data table, plus AuditUser and AuditTimestamp. A single user cannot alter the same record in two different ways at the exact same moment. In fact, I don’t think any two users could, but I’ll let it slide.

The code could probably be improved to automatically include some common indexes on the audit tables.

I’m sure this is the worst possible way to generate code, but I wanted something non-tool specific. I thought about T4 templates since everyone probably has Visual Studio, but this was quicker than figuring out how to get a full NHibernate configuration in to a T4 template.

Expanding the idea

You can definitely build on this idea to generate all sorts of repetitive code based on your NHibernate schema. For instance, if you’re unlucky enough to work in a shop with one of those stored-proc crazed database Nazis, this should help quite a bit. You can use this same idea to generate thousands of CRUD stored procedures in a few minutes.

Views and Databases Don’t Mix

The Problem

In my MVC applications, I sometimes bind my views directly to NHibernate entities. a lot. like always. I also do session-per-request.

I don’t remember what prompted this thought – probably Twitter – but it seemed questionable to access the database from the view by loading a lazy collection. I knew you shouldn’t put database access code in the view, but this would just be a property, nothing complex at all. Ayende set me straight.

You should avoid it. It is dangerous to do loading in the
view, it is subject to too many changes.

How do we prevent it? Of course, the simple answer is “just don’t do it.” Anything more quickly falls in the category of protecting you from yourself, of which I’m usually not a fan. Still, this is easy to overlook. The Morts of the world – and me – will fall in to this trap easily.

ViewModel Solution

What if we had some sort of model, but instead of being generic for the entire application, it only dealt with the concerns of a specific view. Oh, and what if we called it something weird like viewmodel.

This is the best way to avoid problems. Instead of binding directly to an entity, each view should have a corresponding viewmodel class, that contains all of the data being pushed down to the view as well as all the data collected by the view from the user. It’s a POCO specific to the view, with no association to the NHibernate session, so it can’t accidentally load up some data.

You can easily test your viewmodels. Since they also make your views pretty darn stupid, you can maybe skip some of that time-consuming UI testing. Well, skip it safely this time. ViewModels also work well for all that validation attribute markup. Just sayin’.

This isn’t the point of my post today, and what I’ve described isn’t the traditional Model-View-ViewModel. It’s some weird hybrid of separated presentation patterns. You can read more about view models here and here and on Jose’s Chinook Media Manager application series.

Exploding View Solution

image While I can’t say it’s worse than something silently breaking, I don’t like the idea of something bad silently working. Fragile code only works until the worst possible moment.

So, without going for a full ViewModel implementation, I thought it would be a good thing (or at least slightly better) to make NHibernate throw an exception when we hit the DB in the view so we have to fix it now while we’re debugging instead of later. I initially had thoughts about interceptors or connection providers and all sorts of craziness. Ayende’s answer is simply to close the session at the controller boundary.

How do we implement this? We close the session between the controller action and the view. So, instead of session per (the entire) request, we trim the scope at the end just a bit.

Let’s take a high-level look at a chunk of the ASP.NET MVC lifecycle:

  1. A controller action is chosen based on the request and action filters like HttpGet or HttpPost.
  2. The action (the actual controller method) executes, returning an action result – a ViewResult, RedirectToActionResult, ContentResult, or some other built-in or custom action result.
  3. The action result executes. In the case of a ViewResult, the view is rendered down to actual HTML and written out to the response stream.

We need to close down the session between #2 and #3. Lucky for us, every controller has an overloadable / overridable method called OnResultExecuting. This method gets called just before the action result is executed. We can simply override this method in our application’s base controller class. You have one of those, right? They’re handy for all sorts of stuff.

Just close down any session we may have open inside OnResultExecuting. Considering the references, this takes a little bit of plumbing, but I’ll leave that up to you since it’s dependent on your method of opening and tracking NHibernate sessions through the request.

One last thing

Maybe in a later version of NH Profiler, the problem I described today will trigger an alert. Maybe not.

If you’re using NHibernate without NH Profiler, you’re either writing substandard code or wasting time – probably both. If you don’t believe me, download a trial and see what it tells you about your last NHibernate app.

Tags:

Authentication, Impersonation, and Dynamic NHibernate Connection Strings

A web site I recently worked on has two major parts. The first is internet-facing and allows customers to create data records. The other is for internal use to allow office staff to alter data created by the customer.

SQL Audit Setup

The database uses MS SQL Server trigger-based audit logs to record data changes with a username and timestamp. They are literally for audit purposes, and not used in the application. Every database table has a matching Audit table with exactly the same fields plus a username, change type, and timestamp field.

Only updates and deletes trigger an entry in the audit log. The data recorded in the log represents the record prior to the change. The username field is defaulted to SYSTEM_USER, a SQL server specific variable that returns the current account name associated with the connection. This can be either a SQL server account or a windows account. Change type is a single character U or D depending on the action being performed. Timestamp is a datetime field defaulted to GETDATE().

In order to associate audit log records with a particular username, all updates and deletes must run in the context of the user.

Authentication and Impersonation

Customers do not log in to the website. There is no authentication. When a customer interacts with the database, we used a generic SQL account specified in the connection string. All customer changes are recorded with the name of this generic account.

When the office staff uses the website, they are required to authenticate with their windows accounts. This account is impersonated all the way to the database so the audit log will record the specific user name.

There are several ways to achieve this impersonation. The option I chose to use is basic authentication. Over SSL (an https website) this is just as secure as any other data. The primary drawback is that users will get the standard, generic popup log in box. Since the username and password are transmitted to the web server, the web server can easily impersonate this user to any resource, including SQL, even on another server.

You can also run Windows authentication. This was referred to as NTLM back in the day. The benefit of this over basic authentication is that Internet Explorer will authenticate with the web server automatically using the current user’s windows credentials. Since this authentication is done with public keys instead of passwords, the web server can’t impersonate the user to resources (including SQL server) on other servers. This means it’s not really a working solution by itself. If you’re running SQL server on your production web server, stop reading now and go fix that.

Fortunately, windows authentication can take advantage of delegated Kerberos authentication. This is difficult to set up and well beyond the scope of this post. Basically, after trading some more public keys around, the SQL server knows that the web server is telling the truth about the current user and agrees to run the SQL statements in the context of that user. Internet Explorer users are still not prompted for their user names and passwords, and it just works. 

Warning: Impersonation, no matter which method you choose, is not recommended. It effectively disables connection pooling, which stops your application from scaling. In fact, this whole post is probably one bad idea after another.

In this particular case, I’m safe. We can’t physically fit more than a dozen “staff” users, and we’ll certainly never get approval to hire even that many. I’ve used this same architecture for hundreds of users before I knew any better – and the hardware was much slower. In short, it’ll work in this case.

Dynamic Connection Strings

This led to another small issue. Customers should log in with a generic SQL account. Staff should log in with their windows accounts.

Jose R. led me to this article on the NHForge wiki from November 2008. Well, things have changed slightly in the last year. Also, I wanted something a little more pluggable.

NHibernate uses a DriverConnectionProvider to supply it with a connection. To alter the connection string as needed, we only need to override the ConnectionString property of this class.

using NHibernate.Connection;

namespace DynamicConnectionStringExample
{
    public class DynamicConnectionProvider : DriverConnectionProvider
    {

        public static IConnectionStringProvider connectionStringProvider;

        protected override string ConnectionString
        {
            get
            {
                if (null!=connectionStringProvider)
                {
                    return connectionStringProvider.GetConnectionString();
                }
                return base.ConnectionString;
            }
        }

    }
}

Note: The example I show here assumes you are using one of the standard bytecode providers such as NHibernate.Bytecode.Castle. However, you could very easily use an IoC bytecode provider (except Ninject) from uNHAddIns and use constructor dependency injection to inject the IConnectionStringProvider dependency. If not, you’ll need to manually set the ConnectionStringProvider.

To use this, just tell NHibernate about it:

Environment.Properties[Environment.ConnectionProvider] = typeof(DynamicConnectionProvider).AssemblyQualifiedName;

Of course, the “magic” really happens in the IConnectionStringProvider. If we have an authenticated user, GetConnectionString() returns a connection string with Integrated Security = true. If we don’t, we return one with a SQL username and password.

Tags: ,

NHibernate Testing with SQLite in-memory DB

SQLite LogoThis is a follow-up to my post here about SQLiteDatabaseScope, a small class to control the lifecycle of SQLite in-memory databases. It allows you to run NHibernate tests against SQLite’s fast in-memory database. Since each test can have its own database in memory, you can easily run tests in parallel without conflict.

Warning: While SQLite is thread-safe, SQLiteDatabaseScope itself is not. It is intended to be used to manage one or more SQLite in-memory databases per thread, not shared across threads.

Since I originally wrote SQLiteDatabaseScope, I’ve learned a few things about SQLite and NHibernate, and updated the code to match.

First, building an NHibernate configuration and session factory is heavy. It’s a huge waste of time to rebuild those objects for each new instance of the database. Instead, you supply your own configuration and session factory.

Second, it’s possible to create an in-memory database with initial data. A few people I follow on Twitter mentioned giving up on SQLite because they couldn’t quickly and easily fill the in-memory database with necessary initial test data. I’ve found a solution. We attach a SQLite file database containing our data, perform an “INSERT INTO tbl SELECT * FROM attached.tbl” command for each table, then detach our file database. The import runs fast, and the file database can be safely accessed by multiple threads simultaneously, so we can still run our tests in parallel. Better yet, since SQLite doesn’t support foreign keys, we don’t have to be careful about the table order when we import.

Here’s the code:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.SQLite;

namespace System.Data.SQLite
{
    public class SQLiteDatabaseScope : IDisposable
    {

        private const string CONNECTION_STRING = "Data Source=:memory:;Version=3;New=True;";

        private static readonly log4net.ILog Log = log4net.LogManager.GetLogger(typeof(SQLiteDatabaseScope));

        private object sync = new object();
        private NHibernate.Cfg.Configuration config;
        private NHibernate.ISessionFactory sessionFactory;
        private string initialDataFilename;
        private SQLiteConnection connection;

        public SQLiteDatabaseScope(NHibernate.Cfg.Configuration Configuration,
            NHibernate.ISessionFactory SessionFactory)
        {
            Log.Info("Creating database scope");
            config = Configuration;
            sessionFactory = SessionFactory;
        }

        public SQLiteDatabaseScope(NHibernate.Cfg.Configuration Configuration,
            NHibernate.ISessionFactory SessionFactory,
            string InitialDataFilename)
            : this(Configuration, SessionFactory)
        {
            initialDataFilename = InitialDataFilename;
        }

        public NHibernate.ISession OpenSession()
        {
            return sessionFactory.OpenSession(GetConnection());
        }

        public NHibernate.ISession OpenSession(NHibernate.IInterceptor Interceptor)
        {
            return sessionFactory.OpenSession(GetConnection(), Interceptor);
        }

        public NHibernate.IStatelessSession OpenStatelessSession()
        {
            return sessionFactory.OpenStatelessSession(GetConnection());
        }

        private SQLiteConnection GetConnection()
        {
            if (null == connection)
                BuildConnection();
            return connection;
        }

        private void BuildConnection()
        {
            Log.Info("Building SQLite database connection");
            connection = new SQLiteConnection(CONNECTION_STRING);
            connection.Open();
            BuildSchema();
            if (!string.IsNullOrEmpty(initialDataFilename))
                new SQLiteDataLoader(connection, initialDataFilename).ImportData();
        }

        private void BuildSchema()
        {
            Log.Debug("Creating schema");
            NHibernate.Tool.hbm2ddl.SchemaExport se;
            se = new NHibernate.Tool.hbm2ddl.SchemaExport(config);
            se.Execute(false, true, false, connection, null);
        }

        private bool disposedValue = false;

        protected void Dispose(bool disposing)
        {
            if (!this.disposedValue)
            {
                if (disposing)
                {
                    Log.Info("Disposing database scope.");
                    if (null != connection)
                    {
                        connection.Dispose();
                    }
                }
            }
            this.disposedValue=true;
        }

        #region IDisposable Members

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion
    }
}
using System;
using System.Linq;
using System.Data;
using System.Data.SQLite;

namespace System.Data.SQLite
{
    public class SQLiteDataLoader
    {

        private static log4net.ILog Log = log4net.LogManager.GetLogger(typeof(SQLiteDataLoader));

        private const string ATTACHED_DB = "zxcvbnmInitialData";

        private SQLiteConnection connection;
        private string initialDataFilename;

        public SQLiteDataLoader(SQLiteConnection Connection,
            string InitialDataFilename)
        {
            connection = Connection;
            initialDataFilename = InitialDataFilename;
        }

        public void ImportData()
        {
            DataTable dt = connection.GetSchema(SQLiteMetaDataCollectionNames.Tables);
            var tableNames = (from DataRow R in dt.Rows
                             select (string)R["TABLE_NAME"]).ToArray();
            AttachDatabase();
            foreach (string tableName in tableNames)
            {
                CopyTableData(tableName);
            }
            DetachDatabase();
        }

        private void AttachDatabase()
        {
            SQLiteCommand cmd = new SQLiteCommand(connection);
            cmd.CommandText = String.Format("ATTACH '{0}' AS {1}", initialDataFilename, ATTACHED_DB);
            Log.Debug(cmd.CommandText);
            cmd.ExecuteNonQuery();
        }

        private void DetachDatabase()
        {
            SQLiteCommand cmd = new SQLiteCommand(connection);
            cmd.CommandText = string.Format("DETACH {0}", ATTACHED_DB);
            Log.Debug(cmd.CommandText);
            cmd.ExecuteNonQuery();
        }

        private void CopyTableData(string TableName)
        {
            int rowsAffected;
            SQLiteCommand cmd = new SQLiteCommand(connection);
            cmd.CommandText = string.Format("INSERT INTO {0} SELECT * FROM {1}.{0}", TableName, ATTACHED_DB );
            Log.Debug(cmd.CommandText);
            rowsAffected = cmd.ExecuteNonQuery();
            Log.InfoFormat("{0} {1} rows loaded", rowsAffected, TableName);
        }

    }
}

Jason

- We now return you to your regularly scheduled program.

Part 9: NHibernate transactions

In this part, we’re going to wrap our NHibernate transactions and create a factory for them so we can use them in higher layers without referencing NHibernate all the way up.

If you’re new to the series, you can read Part 1, Part 2, Part 3, Part 4, Part 5, Part 6, Part 7, and Part 8 to catch up.

You may have noticed in part 8 that in each DAO method, if we didn’t already have an explicit transaction, I created one around each database interaction. My reason for this is explained in Ayende’s NHibernate Profiler alert “Use of implicit transactions is discouraged.” This works great for simple DB interaction, but what about the more complex scenarios?

This is where we get to talk about this great thing called a business transaction. So once again, I’m going to parade out my experts. Actually, this time it’s only Udi Dahan. There are two key points he’s written about on his blog.

  1. Partial failures can be good. The programmer in all of us sees that and screams atomicity. Transactions  should be all-or-nothing. Anything less is just wrong. Right? In real life, there are instances where we allow, and even prefer partial failures of business transactions. Udi gives us a great example. Would you leave the grocery store empty handed simply because they were out of one item on your list? Probably not. When you’re gathering requirements, be sure to ask questions about the proper way to fail. “Roll it all back” isn’t the only option.
  2. Realistic Concurrency – The entire post is worth reading, but Udi makes one point I want to touch on specifically. When performing an operation for the user, you should get the current state, validate, and then perform the task all within the business transaction.

Let’s use our college application as an example. We have a user story / use case / requirement / story card / whatever to allow students to register for classes, provided those classes aren’t full. If you’ve ever worked at or attended a college or university where certain classes always have more demand than available seats, you are no doubt aware of how quickly those classes will fill up. In fact, the best sections (best professors and best times) can fill up just minutes after registration is opened. It’s very possible that dozens of potential students could access the section when there is only a few seats left. Since the enrollment in a particular section (the current state) changes so rapidly, you must obtain a lock, refresh your enrollment numbers and make sure there is room (revalidate) before actually enrolling that student. If more than one registration request is received, they should be performed serially.

The process is:

  1. Open a transaction at the proper isolation level. Consult your nearest DBA, as isolation levels are outside the scope of this series.
  2. Refresh – Get the current state of the entity
  3. (Re)Validate – Be sure the business transaction is still valid for the current state
  4. Execute – Perform the insert / update / delete
  5. Commit the transaction

Now that we’ve covered business transactions, let’s get set up to use them in our business logic. We shouldn’t have NHibernate types floating around at that level, so we’ll wrap them. Once again, the interfaces go in the Data namespace of the core project and the implementations go in the Data project.

Imports System.Data

Namespace Data

    Public Interface ITransactionFactory

        Function BeginTransaction() As ITransaction
        Function BeginTransaction(ByVal IsolationLevel As IsolationLevel) As ITransaction

    End Interface

End Namespace

Namespace Data

    Public Interface ITransaction
        Inherits IDisposable

        Sub Commit()
        Sub Rollback()

    End Interface

End Namespace

Imports NHibernate

Public Class TransactionFactoryImpl
    Implements ITransactionFactory

    Public Sub New(ByVal Session As ISession)
        m_Session = Session
    End Sub

    Protected ReadOnly m_Session As ISession

    Public Function BeginTransaction() As ITransaction Implements ITransactionFactory.BeginTransaction
        Return New TransactionWrapper(m_Session.BeginTransaction)
    End Function

    Public Function BeginTransaction(ByVal IsolationLevel As System.Data.IsolationLevel) As ITransaction Implements ITransactionFactory.BeginTransaction
        Return New TransactionWrapper(m_Session.BeginTransaction(IsolationLevel))
    End Function

End Class

Imports NHibernate

Public Class TransactionWrapper
    Implements ITransaction

    Public Sub New(ByVal Transaction As NHibernate.ITransaction)
        m_Transaction = Transaction
    End Sub

    Protected ReadOnly m_Transaction As NHibernate.ITransaction

    Public Sub Commit() Implements ITransaction.Commit
        m_Transaction.Commit()
    End Sub

    Public Sub Rollback() Implements ITransaction.Rollback
        m_Transaction.Rollback()
    End Sub

    Private disposedValue As Boolean = False        ' To detect redundant calls

    ' IDisposable
    Protected Overridable Sub Dispose(ByVal disposing As Boolean)
        If Not Me.disposedValue Then
            If disposing Then
                ' TODO: free other state (managed objects).
                m_Transaction.Dispose()
            End If

            ' TODO: free your own state (unmanaged objects).
            ' TODO: set large fields to null.
        End If
        Me.disposedValue = True
    End Sub

#Region " IDisposable Support "
    ' This code added by Visual Basic to correctly implement the disposable pattern.
    Public Sub Dispose() Implements IDisposable.Dispose
        ' Do not change this code.  Put cleanup code in Dispose(ByVal disposing As Boolean) above.
        Dispose(True)
        GC.SuppressFinalize(Me)
    End Sub
#End Region

End Class
using System.Data;

namespace NStackExample.Data
{
    public interface ITransactionFactory
    {

        ITransaction BeginTransaction();
        ITransaction BeginTransaction(IsolationLevel isolationLevel);

    }
}

using System;

namespace NStackExample.Data
{
    public interface ITransaction : IDisposable
    {
        void Commit();
        void Rollback();
    }
}

using System.Data;
using NHibernate;

namespace NStackExample.Data
{
    public class TransactionFactoryImpl : ITransactionFactory
    {

        public TransactionFactoryImpl(ISession Session)
        {
            m_Session = Session;
        }

        protected readonly ISession m_Session;

        #region ITransactionFactory Members

        public ITransaction BeginTransaction()
        {
            return new TransactionWrapper(m_Session.BeginTransaction());
        }

        public ITransaction BeginTransaction(IsolationLevel isolationLevel)
        {
            return new TransactionWrapper(m_Session.BeginTransaction(isolationLevel));
        }

        #endregion
    }
}

using System;
using NHibernate;

namespace NStackExample.Data
{
    public class TransactionWrapper : ITransaction
    {

        public TransactionWrapper(NHibernate.ITransaction Transaction)
        {
            m_Transaction = Transaction;
        }

        protected readonly NHibernate.ITransaction m_Transaction;

        #region ITransaction Members

        void ITransaction.Commit()
        {
            m_Transaction.Commit();
        }

        void ITransaction.Rollback()
        {
            m_Transaction.Rollback();
        }

        private bool disposedValue = false;

        protected override void Dispose(bool Disposing)
        {
            if (!this.disposedValue)
            {
                m_Transaction.Dispose();
            }
            this.disposedValue = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion
    }
}

You may be interested to know that the NHibernate ITransaction will perform an implicit rollback when it is disposed, unless an explicit call to Commit or Rollback has already occurred. To implement this behavior, we implement IDisposable in our transaction wrapper and chain our wrapper’s Dispose to NHibernate.ITransaction’s Dispose. This implicit rollback can indicate a missing call to Commit, so it generates an alert in NHibernate Profiler. If you intend to rollback, do it explicitly. Your code will be easier to understand.

That’s it for part 9.

Jason

- Off to mow the lawn.

Part 8: DAOs, Repositories, or Query Objects

Part 8 is about abstracting NHibernate. Catch up by reading Part 1, Part 2, Part 3, Part 4, Part 5, Part 6, and Part 7.

Warning: This post will contain an extraordinary number of links. They will lead you to the opinions of very smart people™. Click them. Read them. Learn something new.
There is no one best practice. I know. I googled for it. It seems there are just as many patterns as there are anti-patterns. In fact, these days we’re not even clear which is which. There are differing opinions all over the place.

What are my options?

Repositories

Data Access Objects:

Query objects:

  • In early 2009, Ayende posted that he no longer likes repositories, and has switched to query objects which expose raw NHibernate  ICriteria.
  • Udi Dahan also prefers query objects

    One note about all of this: Repositories and DAOs can both be used with query objects or simple FindBy methods. Query objects can also be used on their own.

    What’s the score?

    The experts don’t agree, so use whatever you think will work best for your application and your team. By the way, if you’re not following all of these people on twitter, go follow them now.

    If you’re looking for a good NHibernate repository sample, check out Your First NHibernate based application by Gabriel Schenker on the NHForge wiki, or José’s Chinook WPF app.

    In this sample application, we’re going to use Data Access Objects. The pattern is simple and well known. This application is small and we won’t have many queries, so we’ll use DAOs with FindBy methods. In a large project, such as an ERP, I would use query objects.

    Splitting the CRUD

    CRUD stands for create, read/retrieve, update, and delete/destroy. which correspond to SQL INSERT, SELECT, UPDATE, and DELETE respectively.

    Suppose we’re tracking down an issue in our system where the customer’s middle name was being erased from the database. You start with the most likely locations such as the round trip through the customer update view. No luck. You’ll have to dig in deeper.

    We’re using constructor dependency injection throughout our application. Our DAO is defined by the interface IDAO<T>. If you saw some object with a dependency of IDAO<Customer>, you would assume that it performs some database action on customer, so it would be a candidate for deeper investigation. Of course, without diving in to the code, you wouldn’t know what it actually does to customer.

    As it turns out 95% of the uses of IDAO<Customer> only display customer data. They don’t actually change anything. You just wasted a LOT of time digging through code that couldn’t possibly cause your bug.

    Now suppose you had split your IDAO interface to allow more fine-grained dependencies. Instead of IDAO<T>, you now have ICreate<T>, IRead<T>, IUpdate<T>, and IDelete<T>. When searching for a bug like the one I described, you only need to search through classes with dependencies on IUpdate<Customer> and possibly ICreate<Customer>.

    We’re tracking which entity instances are transient (new, not saved) and which ones are already persisted (saved to the database) by the ID property. If the ID is equal to Guid.Empty, the instance is transient. If the ID has any other value, it’s persistent. Since we know that handy bit of information, we don’t really need separate interfaces for create and update operations. We can combine them in to one called ISave<T>. We now have IRead<T>, ISave<T>, and IDelete<T>.

    Even though we’ve split our interface up by operation, we’re still only going to have one DAO implementation. In the Ninject module, we’ll bind each of our three interfaces to the DAO implementation.

    Every entity has the same basic CUD, but what about entity-specific queries? In these cases, we’ll create entity-specific interfaces such as IReadCustomer. This means you could have up to four IoC bindings for each entity.

    Splitting the CRUD operations in to separate interfaces has one added benefit. In our case, we don’t want to allow certain (most) entities to be deleted. In these cases, your entity-specific DAO shouldn’t implement IDelete. For this reason, we won’t implement deletes in our generic base DAO.

    Show me some code already!

    We put our interfaces in the data namespace of the core project and our implementations in the data project.

    Namespace Data
    
        Public Interface IRead(Of TEntity As Entity)
    
            Function GetByID(ByVal ID As Guid) As TEntity
    
        End Interface
    
    End Namespace
    
    Namespace Data
    
        Public Interface ISave(Of TEntity As Entity)
    
            Function Save(ByVal Entity As TEntity) As TEntity
    
        End Interface
    
    End Namespace
    
    Namespace Data
    
        Public Interface IDelete(Of TEntity As Entity)
    
            Sub Delete(ByVal Entity As TEntity)
    
        End Interface
    
    End Namespace
    
    Namespace Data
    
        Public Interface IReadStudent
            Inherits IRead(Of Student)
    
            Function FindByStudentID(ByVal StudentID As String) As Student
            Function FindByName(ByVal LikeFirstName As String, ByVal LikeLastName As String) As IEnumerable(Of Student)
    
        End Interface
    
    End Namespace
    
    Imports NHibernate
    
    Public Class GenericDAOImpl(Of TEntity As Entity)
        Implements IRead(Of TEntity)
        Implements ISave(Of TEntity)
    
        Public Sub New(ByVal Session As ISession)
            m_session = Session
        End Sub
    
        Protected ReadOnly m_Session As ISession
    
        Public Function GetByID(ByVal ID As System.Guid) As TEntity Implements IRead(Of TEntity).GetByID
            If m_Session.Transaction Is Nothing Then
                Dim RetVal As TEntity
                Using Tran = m_Session.BeginTransaction
                    RetVal = m_Session.Get(Of TEntity)(ID)
                    Tran.Commit()
                    Return RetVal
                End Using
            Else
                Return m_Session.Get(Of TEntity)(ID)
            End If
        End Function
    
        Public Function Save(ByVal Entity As TEntity) As TEntity Implements ISave(Of TEntity).Save
            If m_Session.Transaction Is Nothing Then
                Using Tran = m_Session.BeginTransaction
                    m_Session.SaveOrUpdate(Entity)
                    Tran.Commit()
                End Using
            Else
                m_Session.SaveOrUpdate(Entity)
            End If
            Return Entity
        End Function
    
    End Class
    
    Imports NHibernate
    Imports NHibernate.Criterion
    
    Public Class StudentDaoImpl
        Inherits GenericDAOImpl(Of Student)
        Implements IReadStudent
    
        Public Sub New(ByVal Session As ISession)
            MyBase.New(Session)
        End Sub
    
        Public Function FindByName(ByVal LikeFirstName As String, ByVal LikeLastName As String) As System.Collections.Generic.IEnumerable(Of Student) Implements IReadStudent.FindByName
            Dim crit As ICriteria = m_Session.CreateCriteria(Of Student) _
                .Add(Expression.Like("FirstName", LikeFirstName)) _
                .Add(Expression.Like("LastName", LikeLastName)) _
                .SetMaxResults(101)
            If m_Session.Transaction Is Nothing Then
                Using Tran = m_Session.BeginTransaction()
                    Dim RetVal = crit.List.Cast(Of Student)()
                    Tran.Commit()
                    Return RetVal
                End Using
            Else
                Return crit.List.Cast(Of Student)()
            End If
        End Function
    
        Public Function FindByStudentID(ByVal StudentID As String) As Student Implements IReadStudent.FindByStudentID
            Dim Crit = m_Session.CreateCriteria(Of Student) _
                .Add(Expression.Eq("StudentID", StudentID))
            If m_Session.Transaction Is Nothing Then
                Using Tran = m_Session.BeginTransaction
                    Dim RetVal = Crit.UniqueResult(Of Student)()
                    Tran.Commit()
                    Return RetVal
                End Using
            Else
                Return Crit.UniqueResult(Of Student)()
            End If
        End Function
    
    End Class
    using System;
    
    namespace NStackExample.Data
    {
        public interface IRead<TEntity> where TEntity : Entity
        {
    
            TEntity GetById(Guid ID);
    
        }
    }
    
    namespace NStackExample.Data
    {
        public interface ISave<TEntity> where TEntity : Entity
        {
    
            TEntity Save(TEntity entity);
    
        }
    }
    
    namespace NStackExample.Data
    {
        public interface IDelete<TEntity> where TEntity:Entity
        {
    
            void Delete(TEntity entity);
    
        }
    }
    
    using System.Collections.Generic;
    
    namespace NStackExample.Data
    {
        public interface IReadStudent : IRead<Student>
        {
    
            Student FindByStudentID(string StudentID);
            IEnumerable<Student> FindByName(string LikeFirstName, string LikeLastName);
    
        }
    }
    
    using NHibernate;
    
    namespace NStackExample.Data
    {
    
        public class GenericDAOImpl<TEntity> : IRead<TEntity>, ISave<TEntity> where TEntity : Entity
        {
    
            public GenericDAOImpl(ISession Session)
            {
                m_Session = Session;
            }
    
            protected readonly ISession m_Session;
    
            public TEntity GetByID(System.Guid ID)
            {
                if (m_Session.Transaction == null)
                {
                    TEntity retval;
                    using (var Tran = m_Session.BeginTransaction())
                    {
                        retval = m_Session.Get<TEntity>(ID);
                        Tran.Commit();
                        return retval;
                    }
                }
                else
                {
                    return m_Session.Get<TEntity>(ID);
                }
            }
    
            public TEntity Save(TEntity Entity)
            {
                if (m_Session.Transaction == null)
                {
                    using (var Tran = m_Session.BeginTransaction())
                    {
                        m_Session.SaveOrUpdate(Entity);
                        Tran.Commit();
                    }
                }
                else
                {
                    m_Session.SaveOrUpdate(Entity);
                }
                return Entity;
            }
    
        }
    
    }
    
    using NHibernate;
    using NHibernate.Criterion;
    using System.Collections.Generic;
    using System.Linq;
    
    namespace NStackExample.Data
    {
        public class StudentDAOImpl : GenericDAOImpl<Student>, IReadStudent
        {
    
            public StudentDAOImpl(ISession Session) : base(Session) { }
    
            public System.Collections.Generic.IEnumerable<Student> FindByName(string LikeFirstName, string LikeLastName)
            {
                ICriteria crit = m_Session.CreateCriteria<Student>()
                    .Add(Expression.Like("FirstName", LikeFirstName))
                    .Add(Expression.Like("LastName", LikeLastName))
                    .SetMaxResults(101);
                if (m_Session.Transaction == null)
                {
                    using (var Tran = m_Session.BeginTransaction())
                    {
                        var RetVal = crit.List().Cast<Student>();
                        Tran.Commit();
                        return RetVal;
                    }
                }
                else
                {
                    return crit.List().Cast<Student>();
                }
            }
    
            public Student FindByStudentID(string StudentID)
            {
                var Crit = m_Session.CreateCriteria<Student>()
                    .Add(Expression.Eq("StudentID", StudentID));
                if (m_Session.Transaction == null)
                {
                    using (var Tran = m_Session.BeginTransaction())
                    {
                        var RetVal = Crit.UniqueResult<Student>();
                        Tran.Commit();
                        return RetVal;
                    }
                }
                else
                {
                    return Crit.UniqueResult<Student>();
                }
            }
    
        }
    }

    Other changes

    I’ve cleaned out the course and student DAO junk from part 7. These were just used to illustrate session-per-request.

    The fluent mapping classes have been moved in to a mapping folder.

    That’s it for part 8. Don’t forget to write your tests for the queries.

    Jason

    - IBlog.Post(Part8) operation completed. Executing IWatchTV.Watch(Timespan.FromHours(1))

  • Part 7: NHibernate and Ninject for ASP.NET MVC

    In part 6, I explained how to set up Ninject with ASP.NET MVC. In this part, we’ll add NHibernate to the mix. Specifically, we’re going to set up session-per-request using a Ninject and bind all the necessary NHibernate interfaces.

    Of course, for the sake of history, read up on part 1, part 2, part 3, part 4, part 5, and part 6.

    If you aren’t familiar with NHibernate in an ASP.NET MVC application, the most common way to manage your sessions is to open one session per web request. Just about everything you need to know about session-per-request is explained in the content and comments of this post on Ayende’s blog, but I’ll summarize for you.

    • While building a session factory may be a big operation, once it’s built, opening a session is lightweight.
    • Opening a session does not open a connection to the database
    • NHibernate has a built in method for doing session-per-request, but Ayende doesn’t use it for simple stuff and neither will we. When your application doesn’t do anything other than session-per-request, it’s just easier to do it this way.
    • Multiple business transactions and therefore multiple sessions in a single web request are usually not necessary, just because of how users tend to interact with the application. Even then, you can usually accomplish the same thing with multiple DB transactions on the same session.

    SessionPerConversation

    NHibernate Burrow is available to help with complex session management in web apps where session per conversation is used. Basically, this allows you to span your NHibernate sessions across several web requests. Just a quick note: If you disregarded everyone’s advice and used Identity (integer auto-number) ID fields, Burrow won’t work for you. If you want more information, check out the Burrow posts on NHForge. Also,  Jose Romaniello’s uses Conversation per Business Transaction in his NHibernate and WPF series on NHForge.org. It’s definitely worth a read.

    OK. Back to session-per-request. I’m taking a slightly different approach than Ayende. Even though opening a session is lightweight, I don’t like the idea of opening a session for requests that may not use NHibernate at all. For example, in an application I’m building at work, only about 7 views out of nearly 50 actually use an NHibernate session. That’s a lot of unused sessions.

    First things first, we need to make a Ninject module for all of our NHibernate bindings. Where are we going to put it? We have two options. We could put it in NStackExample.Data with all of our NHibernate mappings and configuration. We could also put it in NStackExample.Web. Like Ayende, we will be storing the NHibernate session in the context of the current web request and relying on our application’s EndRequest event to close the session. Since we’re unfortunately coupled to the web application, we’ll put it in the web project.

    1. In the web project, make a new folder called Code.
    2. Make a class in that folder called NHibernateModule.
    3. NHibernateModule should inherit from Ninject.Core.StandardModule.

    The process of configuring NHibernate is a lot of work and only needs to be done once. Since our configuration object also creates the session factory, another potentially heavy operation, we kill two birds with one stone. The binding for our NHibernate configuration looks like this:

        Public Overrides Sub Load()
            Dim Cfg As New NStackExample.Data.Configuration
            Cfg.Configure()
    
            Bind(Of NStackExample.Data.Configuration).ToConstant(Cfg)
        End Sub
        public override void Load()
        {
            NStackExample.Data.Configuration Cfg = new NStackExample.Data.Configuration()
            Cfg.Configure();
    
            Bind().ToConstant(Cfg);
        }

    ToConstant bindings essentially create singletons, at least within the scope of our Ninject kernel. Unlike true singletons, this isn’t evil because our tests are free to mock, replace, and re-implement them as necessary.

    Now that we have NHibernate configured and our session factory built, we need to bind our NHibernate session. The scope of our session is somewhat complex (per-request). We could use the OnePerRequestBehavior of Ninject, but that requires the registration of an IIS HTTP module. Instead, we’ll just bind it to a method and manage it ourselves. This method will create up to one session per request. If a particular request doesn’t require a session, Ninject will never call the method, so an unnecessary session won’t be created. If a particular request asks for a session more than once, perhaps to build more than one DAO, the method will create a single session and use it throughout the web request. Here’s what our module looks like with the binding for our session:

        Friend Const SESSION_KEY As String = "NHibernate.ISession"
    
        Public Overrides Sub Load()
            Dim Cfg As New Configuration
            Cfg.Configure()
    
            Bind(Of Configuration).ToConstant(Cfg)
            Bind(Of NHibernate.ISession).ToMethod(AddressOf GetRequestSession)
        End Sub
    
        Private Function GetRequestSession(ByVal Ctx As IContext) As NHibernate.ISession
            Dim Dict As IDictionary = HttpContext.Current.Items
            Dim Session As NHibernate.ISession
            If Not Dict.Contains(SESSION_KEY) Then
                'Create an NHibernate session for this request
                Session = Ctx.Kernel.Get(Of Configuration)().OpenSession()
                Dict.Add(SESSION_KEY, Session)
            Else
                'Re-use the NHibernate session for this request
                Session = Dict(SESSION_KEY)
            End If
            Return Session
        End Function
            internal const string SESSION_KEY = "NHibernate.ISession";
    
            public override void Load()
            {
                Configuration Cfg = new Configuration();
                Cfg.Configure();
    
                Bind<Configuration>().ToConstant(Cfg);
                Bind<NHibernate.ISession>().ToMethod(x => GetRequestSession(x));
            }
    
            private NHibernate.ISession GetRequestSession(IContext Ctx)
            {
                IDictionary Dict = HttpContext.Current.Items;
                NHibernate.ISession Session;
                if (!Dict.Contains(SESSION_KEY))
                {
                    // Create an NHibernate session for this request
                    Session = Ctx.Kernel.Get<Configuration>().OpenSession();
                    Dict.Add(SESSION_KEY, Session);
                } else {
                    // Re-use the NHibernate session for this request
                    Session = (NHibernate.ISession) Dict[SESSION_KEY];
                }
                return Session;
            }

    All we have left to do is dispose our session at the end of the request. Let’s go back to the Global.asax codebehind.

        Private Sub MvcApplication_EndRequest(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.EndRequest
            If Context.Items.Contains(NHibernateModule.SESSION_KEY) Then
                Dim Session As NHibernate.ISession = Context.Items(NHibernateModule.SESSION_KEY)
                Session.Dispose()
                Context.Items(NHibernateModule.SESSION_KEY) = Nothing
            End If
        End Sub
            public MvcApplication()
            {
                this.EndRequest += MvcApplication_EndRequest;
            }
    
            private void MvcApplication_EndRequest(object sender, System.EventArgs e)
            {
                if (Context.Items.Contains(NHibernateModule.SESSION_KEY))
                {
                    NHibernate.ISession Session = (NHibernate.ISession) Context.Items[NHibernateModule.SESSION_KEY];
                    Session.Dispose();
                    Context.Items[NHibernateModule.SESSION_KEY] = null;
                }
            }

    To illustrate how this will work, I’ve made several additions to the code download. I’ve added a BaseController and HomeController so we can begin to run our web application. I’ve also added a IStudentDao and ICourseDao interfaces to the core project and corresponding implementations in the Data project. I’ve bound the DAO interfaces to their corresponding implementations and added debug statements to output exactly what’s happening with our session. Finally, I’ve set up a constructor in HomeController making it dependent on IStudentDao and ICourseDao.

    When we run our application, we see from the debug output that the session is created when we create our IStudentDao. The session is reused to create our ICourseDao. This gives us everything we need to create the HomeController. The web request executes. When the request ends, the session is disposed. If you remove one of the Dao dependencies from HomeController, you’ll see that our session is created. It’s not reused because nothing else needs a session. If you remove both of the Dao dependencies from HomeController, you’ll see that our session is never even created. Since we didn’t create a session, we don’t dispose it when the web request ends.

    That’s all for part 7. In part 8, we’ll wrap the NHibernate transaction for use in our controllers project and build a real DAO or two.

    Get your code here! We have VB.NET and CSharp flavored bits.

    Jason

    - NHibernating Ninja Wannabe