NHibernate SchemaValidator reimplemented.

Some time ago a new utility class was introduced to NHibernate. It’s called SchemaValidator and it allows you to detect inconsistencies between mappings and database schema. Here you can find a short sample code showing how it works.

The class has got only one useful method called Validate(). It analyzes and compares database metadata with mappings. It can detect missing tables, missing columns, improper types in mappings, etc. It throws HibernateException immediately after it encounters such a problem. However, this is a serious disadvantage (at least for me), because I would like to know all issues related to my mappings and I don’t want to be surprised by a sudden NH exception telling me that I have forgotten one column in my .HBM file. That is why I have decided to reimplement this class so that it would not throw HibernateException, but rather return a list of possible issues.

The following code is based on original NHibernate.Tool.hbm2ddl.SchemaValidator class. Instead of throwing HibernateException it returns a list of strings. The code was tested with Sql Server 2005.

using System;
using System.Collections.Generic;
using System.Data.Common;
using NHibernate;
using NHibernate.Cfg;
using NHibernate.Dialect;
using NHibernate.Dialect.Schema;
using NHibernate.Engine;
using NHibernate.Id;
using NHibernate.Mapping;
using NHibernate.Tool.hbm2ddl;
using NHibernate.Util;

namespace MyApplication.Server.DAO.SchemaValidation 
{ 
    class SchemaValidator
    {
        private readonly Configuration configuration;
        private readonly IConnectionHelper connectionHelper;
        private readonly Dialect dialect;

        public SchemaValidator(Configuration cfg) : this(cfg, cfg.Properties) { }

        public SchemaValidator(Configuration cfg, IDictionary<string, string> connectionProperties)
        {
            configuration = cfg;
            dialect = Dialect.GetDialect(connectionProperties);
            IDictionary<string, string> props = new Dictionary<string, string>(dialect.DefaultProperties);
            foreach (var prop in connectionProperties)
            {
                props[prop.Key] = prop.Value;
            }
            connectionHelper = new ManagedProviderConnectionHelper(props);
        }

        public SchemaValidator(Configuration cfg, Settings settings)
        {
            configuration = cfg;
            dialect = settings.Dialect;
            connectionHelper = new SuppliedConnectionProviderConnectionHelper(settings.ConnectionProvider);
        }

        public IList<string> Validate()
        {
            try
            {
                DatabaseMetadata meta;
                try
                {
                    connectionHelper.Prepare();
                    DbConnection connection = connectionHelper.Connection;
                    meta = new DatabaseMetadata(connection, dialect, false);
                }
                catch (Exception sqle)
                {
                    throw;
                }

                return ValidateSchema(dialect, meta);
            }
            catch (Exception e)
            {
                throw;
            }
            finally
            {
                try
                {
                    connectionHelper.Release();
                }
                catch (Exception e)
                {
                	throw;
                }
            }
        }
        
        private IList<string> ValidateSchema(
            Dialect dialect, DatabaseMetadata databaseMetadata)
        {
            IList<string> problems = new List<string>();

            string defaultCatalog = PropertiesHelper.GetString(NHibernate.Cfg.Environment.DefaultCatalog, 
                configuration.Properties, null);
            string defaultSchema = PropertiesHelper.GetString(NHibernate.Cfg.Environment.DefaultSchema, 
                configuration.Properties, null);

            IMapping mapping = configuration.BuildMapping();
            ICollection<PersistentClass> list = configuration.ClassMappings;
            foreach (PersistentClass pc in list)
            {
                try
                {
                    var table = pc.Table;
                    if (table.IsPhysicalTable)
                    {
                        ITableMetadata tableInfo = databaseMetadata.GetTableMetadata(
                            table.Name,
                            table.Schema ?? defaultSchema,
                            table.Catalog ?? defaultCatalog,
                            table.IsQuoted);
                        if (tableInfo == null)
                            problems.Add(string.Format("Missing table: {0}", table.Name));
                        else
                            ValidateColumns(problems, table, dialect, mapping, tableInfo);
                    }
                }
                catch (HibernateException ex)
                {
                    problems.Add(ex.Message);
                }
            }

            var persistenceIdentifierGenerators = IterateGenerators(dialect);
            foreach (var generator in persistenceIdentifierGenerators)
            {
                string key = generator.GeneratorKey();
                if (!databaseMetadata.IsSequence(key) && !databaseMetadata.IsTable(key))
                {
                    problems.Add(string.Format("Missing sequence or table: {0}", key));
                }
            }
            return problems;
        }

        private IEnumerable<IPersistentIdentifierGenerator> IterateGenerators(Dialect dialect)
        {
            var generators = new Dictionary<string, IPersistentIdentifierGenerator>();
            string defaultCatalog = PropertiesHelper.GetString(NHibernate.Cfg.Environment.DefaultCatalog,
                configuration.Properties, null);
            string defaultSchema = PropertiesHelper.GetString(NHibernate.Cfg.Environment.DefaultSchema,
                configuration.Properties, null);

            foreach (var pc in configuration.ClassMappings)
            {
                if (!pc.IsInherited)
                {
                    var ig =
                        pc.Identifier.CreateIdentifierGenerator(dialect, defaultCatalog, defaultSchema, (RootClass)pc) as
                        IPersistentIdentifierGenerator;

                    if (ig != null)
                    {
                        generators[ig.GeneratorKey()] = ig;
                    }
                }
            }

            foreach (var collection in configuration.CollectionMappings)
            {
                if (collection.IsIdentified)
                {
                    var ig =
                        ((IdentifierCollection)collection).Identifier.CreateIdentifierGenerator(dialect, defaultCatalog, defaultSchema,
                                                                                                 null) as IPersistentIdentifierGenerator;

                    if (ig != null)
                    {
                        generators[ig.GeneratorKey()] = ig;
                    }
                }
            }

            return generators.Values;
        }


        private void ValidateColumns(
            IList<string> problems, 
            Table table,
            Dialect dialect,
            IMapping mapping,
            ITableMetadata tableInfo)
        {
            IEnumerable<Column> iter = table.ColumnIterator;
            foreach (Column column in iter)
            {
                IColumnMetadata columnInfo = tableInfo.GetColumnMetadata(column.Name);

                if (columnInfo == null)
                {
                    problems.Add(string.Format("Missing column: {0} in {1}", column.Name,
                        NHibernate.Mapping.Table.Qualify(tableInfo.Catalog, tableInfo.Schema, tableInfo.Name)));
                }
                else
                {
                    bool typesMatch = column.GetSqlType(dialect, mapping).ToLower().StartsWith(columnInfo.TypeName.ToLower());
                    if (!typesMatch)
                    {
                        problems.Add(string.Format("Wrong column type in {0} for column {1}. Found: {2}, Expected {3}",
                                                                   NHibernate.Mapping.Table.Qualify(tableInfo.Catalog, tableInfo.Schema, tableInfo.Name),
                                                                   column.Name, columnInfo.TypeName.ToLower(),
                                                                   column.GetSqlType(dialect, mapping)));
                    }
                }
            }
        }

    }
}

Visual Studio Design Mode – another approach.

Some time ago I published here a piece of code that detects the design mode in Visual Studio Designer. The code worked great (as long as you use only MS Visual Studio). However, after some time, with our project getting more and more complex, the solution turned out to be problematic, as its usage is limited only to classes that extend the BaseDesignModeUserControl. If, for instance, you need to extend System.ComponentModel.Component, then you have to duplicate the code (and the rhetorical question is who likes to duplicate the code?)
So, it turns out that we should forget about “the-base-class” approach and use an extension method instead:

    public static class ExtensionMethods
    {
        private static bool? isDesignMode = null;

        [DebuggerStepThrough]
        public static bool IsDesignMode(this object obj)
        {
            if (!isDesignMode.HasValue)
                isDesignMode = (System.Diagnostics.Process.GetCurrentProcess().ProcessName.IndexOf("devenv") != -1);
            return isDesignMode.Value;
        }
    }

It seems that this piece of code may be called anywhere and I hope that this is the final solution of the problem.

NHibernate IUserType for fake database booleans

In the db schema I’m currently working on, boolean values are represented by columns whose type is tinyint nullable (it’s SQL Server 2005). If such a value equals 1, then it is supposed to be True, 0 means False and if it is null then it is False, too.
This time I would like to show how to use NHibernate IUserType interface to enable automatic conversion from a database field to System.Boolean value
NHibernate.UserTypes.IUserType is a very useful interface when you are to handle some strange db schemas in your data access layer. As you can guess, the thing that we need is a class that implements this interface. You can see it below. The most important methods are NullSafeGet(…) and NullSafeSet(…) as they are responsible for conversion ‘logic’.

namespace MyNamespace
{
  public class ByteAsBool : IUserType
  {
      #region IUserType Members

      public object Assemble(object cached, object owner)
      {
          return cached;
      }

      public object DeepCopy(object value)
      {
          return value;
      }

      public object Disassemble(object value)
      {
          return value;
      }

      public int GetHashCode(object x)
      {
          if (x == null)
              return 0;
          return x.GetHashCode();
      }
      public bool IsMutable
      {
         get { return false; }
      }

      // represents conversion on load-from-db operations:
      public object NullSafeGet(System.Data.IDataReader rs, 
             string[] names, object owner)
      {
          var obj = NHibernateUtil.String.
                 NullSafeGet(rs, names[0]);
          if (obj == null)
              return false;
           byte b = 0;
          try
          {
              if (obj is string)
                  b = byte.Parse(obj as string);
              else
                  b = (byte)obj;
          }
          catch (Exception)
          {
              return false;
          }
          return b == 1;
      }

      // represents conversion on save-to-db operations:
      public void NullSafeSet(System.Data.IDbCommand cmd, 
             object value, int index)
      {
          if (value == null)
          {
              ((IDataParameter)cmd.Parameters[index]).Value = 
                    DBNull.Value;
          }
          else
          {
              var boolValue = (bool)value;
              ((IDataParameter)cmd.Parameters[index]).Value = 
                    boolValue ? (byte)1 : (byte)0;
          }
      }
      public object Replace(object original, object target, 
             object owner)
      {
          return original;
      }

      public Type ReturnedType
      {
          get { return typeof(bool); }
      }

      public NHibernate.SqlTypes.SqlType[] SqlTypes
      {
          get { return new[] { SqlTypeFactory.Byte }; }
      }
      #endregion

      bool IUserType.Equals(object x, object y)
      {
          return object.Equals(x, y);
      }
  }
}

The class ByteAsBool can be now used in NHibernate mappings in the following way:

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2"
    assembly="SomeAssembly" namespace="MyNamespace.Types">
  <class name="SomeClass" table="Some_Table" >
  <!-- ... -->
    <property name="active" 
        column="ACTIVE" 
        type="MyNamespace.ByteAsBool, SomeAssembly" access="field" />
  <!-- ... -->
  </class>
</hibernate-mapping>

The mapped class is very simple and looks very nice, as there are no ugly bool? types, just simple System.Boolean:

  public class SomeClass : BaseEntity
  {
     protected bool active;
     // ...
  }

How to fix syntax highlighting problem with carrington WP theme?

Recently I have chosen carrington theme as the default theme for this blog. Also, I have decided to make pieces of my code look more attractive and installed syntax-highlighting plugin.
Unfortunately, snippets in my posts did not look attractive at all, as the white lines appeared between lines of code:

wordpress_problem

After intensive searching on the Internet, I’ve found this post. However, it turned out that this solution does not work with carrington blog theme. But fortunately I’ve managed to work this problem out…

What to do? Open css/typography.css file in the theme’s directory and find the following fragment:

li,
dd,
.tight {
	margin-bottom:9px;
}

Everything you need to do is to change 9px value to 0px. This should fix the problem (as you can see in the snippet above).