static void

NHibernate IUserType

This is the common scenario of having a sql blob that needs to be an image on the domain side. You can also use this for xml which is a clob in sql and needs to be an XmlDocument or XDocument. Remember some less obvious things like enums and boolean YesNo (Y/N) /TrueFalse (T/F) exist already.

Domain Entity

Category is the same as here except the Picture property uses a new domain class called "Picture".

public virtual Picture Picture { get; set; }

Picture is simply a decorator around System.Drawing.Image which includes the format information. It's part of our domain and has no NHibernate dependency - our mapper will use the constructor overload that takes a byte array. Because I'm testing with Northwind there's an ugly hack for the OLE image header.

using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
 
namespace Northwind
{
    /// <summary>
    /// A picture with format information.
    /// </summary>
    [Serializable]
    public class Picture
    {
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        private readonly Image _image;
 
        #region Constructors
        public Picture(byte[] bytes)
        {
            //called by NHibernate
            try
            {
                using (var ms = new MemoryStream(bytes))
                {
                    _image = Image.FromStream(ms);
                }
            }
            catch (ArgumentException) //parameter is not valid
            {
                //has 78-byte OLE header... a Northwind thing :(
                using (var ms = new MemoryStream(bytes, 78, bytes.Length - 78))
                {
                    _image = Image.FromStream(ms);
                }
            }
            ImageFormat = ImageFormat.Bmp; //default
        }
 
        public Picture(Image image, ImageFormat format)
        {
            //construct as simple decorator with format information
            _image = image;
            ImageFormat = format;
        }
        #endregion
 
        public Image Image { get { return _image; } }
        public ImageFormat ImageFormat { get; set; }
 
        /// <summary>
        /// Returns the image as a byte array which can be persisted.
        /// </summary>
        /// <returns></returns>
        public byte[] ToArray()
        {
            using (var ms = new MemoryStream())
            {
                _image.Save(ms, ImageFormat);
                return ms.ToArray();
            }
        }
 
        #region Identity
        public override bool Equals(object obj)
        {
            return _image.Equals(obj);
        }
 
        public override int GetHashCode()
        {
            return _image.GetHashCode();
        }
 
        public override string ToString()
        {
            if (_image != null) return _image.Width + " x " + _image.Height;
            return "Empty";
        }
        #endregion
    }
}

Mapping

The hbm mapping doesn't specify "Picture" as the type, it specifies another class that derives from IUserType.

<property name="Picture" column="`Picture`" type="Northwind.Mappings.PictureMapper, Northwind" />

Alternatively, with parameters (for IParameterizedType)

<property name="Picture" column="`Picture`">
  <type name="Northwind.Mappings.PictureMapper, Northwind">
    <param name="format">Jpeg</param>
  </type>
</property>

Here is the mapping class, derived from IUserType (and IParameterizedType so we can define the format parameter in mapping). The real work is done in NullSafeGet and NullSafeSet. The IParameterizedType interface had an additional method in NHibernate 2.1, which this includes.

using System;
using System.Collections;
using System.Collections.Generic;
using System.Data;
using System.Diagnostics;
using System.Drawing.Imaging;
using NHibernate;
using NHibernate.UserTypes;
 
namespace Northwind.Mappings
{
    /// <summary>
    /// A mapper to tranlate between database byte array and an image (in our Picture decorator).
    /// </summary>
    public class PictureMapper : IUserType, IParameterizedType
    {
        [DebuggerBrowsable(DebuggerBrowsableState.Never)]
        ImageFormat _imageFormat = ImageFormat.Bmp;
 
        #region IUserType Members
 
        public object Assemble(object cached, object owner)
        {
            return DeepCopy(cached);
        }
 
        public object DeepCopy(object value)
        {
            return value;
        }
 
        public object Disassemble(object value)
        {
            return DeepCopy(value);
        }
 
        public new bool Equals(object x, object y)
        {
            if (object.ReferenceEquals(x, y)) return true;
            if (x == null || y == null) return false;
            return x.Equals(y);
        }
 
        public int GetHashCode(object x)
        {
            return x.GetHashCode();
        }
 
        public bool IsMutable
        {
            get { return false; }
        }
 
        public object NullSafeGet(System.Data.IDataReader rs, string[] names, object owner)
        {
            object obj = NHibernateUtil.BinaryBlob.NullSafeGet(rs, names[0]);
            if (obj == null) return null;
            var bytes = obj as byte[];
            var pic = new Picture(bytes); //create from bytes
            pic.ImageFormat = _imageFormat; //the mapping default
            return pic;
        }
 
        public void NullSafeSet(System.Data.IDbCommand cmd, object value, int index)
        {
            var parameter = (IDataParameter)cmd.Parameters[index];
            if (value == null)
                parameter.Value = DBNull.Value;
            else
                parameter.Value = ((Picture)value).ToArray();
        }
 
        public object Replace(object original, object target, object owner)
        {
            return original;
        }
 
        public Type ReturnedType
        {
            //the .Net type that this maps to
            get { return typeof(System.Drawing.Image); }
        }
 
        public NHibernate.SqlTypes.SqlType[] SqlTypes
        {
            //the sql type that this maps to
            get { return new NHibernate.SqlTypes.SqlType[] { NHibernateUtil.BinaryBlob.SqlType }; }
        }
 
        #endregion
 
        #region IParameterizedType Members
 
        /// <summary>
        /// Sets the parameter values. Called if mapping tag in the hbm.
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        public void SetParameterValues(IDictionary parameters)
        {
            if (parameters == null) return;
            /*enables mapping (optional)
             <property name="Picture">
                <type name="PictureMapper">
                    <param name="format">Bmp</param>
                </type>
              </property>
             */
            var value = parameters["format"] as string;
            if (value == null) return;
 
            _imageFormat = TranslateImageFormat(value);
        }
 
        /// <summary>
        /// Sets the parameter values. New from NHibernate 2.1
        /// </summary>
        /// <param name="parameters">The parameters.</param>
        public void SetParameterValues(IDictionary<string, string> parameters)
        {
            if (parameters == null) return;
            if (!parameters.ContainsKey("format")) return;
            var value = parameters["format"] as string;
            if (value == null) return;
 
            _imageFormat = TranslateImageFormat(value);
        }
 
        private ImageFormat TranslateImageFormat(string value)
        {
            switch (value.ToUpper(System.Globalization.CultureInfo.InvariantCulture))
            {
                case "BMP": // the bitmap (BMP) image format.
                    return ImageFormat.Bmp; // bitmap (BMP) image format.
                case "EMF": // the enhanced metafile (EMF) image format.
                    return ImageFormat.Emf; // enhanced metafile (EMF) image format.
                case "EXIF": // the Exchangeable Image File (Exif) format.
                    return ImageFormat.Exif;
                case "GIF": //Gets the Graphics Interchange Format (GIF) image format.
                    return ImageFormat.Gif;
                case "ICON": // the Windows icon image format.
                    return ImageFormat.Icon;
                case "JPEG": // the Joint Photographic Experts Group (JPEG) image format
                    return ImageFormat.Jpeg;
                case "MEMORYBMP": // a memory bitmap image format.
                    return ImageFormat.MemoryBmp;
                case "PNG": // the W3C Portable Network Graphics (PNG) image format.
                    return ImageFormat.Png;
                case "TIFF": // the Tagged Image File Format (TIFF) image format.
                    return ImageFormat.Tiff;
                case "WMF": // the Windows metafile (WMF) image format.
                    return ImageFormat.Wmf;
                default:
                    return ImageFormat.Bmp;
            }
        }
 
        #endregion
    }
}

Unit test

A simple MSTest for it.

[TestMethod, DeploymentItem("Windows XP.jpg")]
public void TestImage()
{
    int categoryId = 0;
 
    var path = Path.Combine(Environment.CurrentDirectory, "Windows XP.jpg");
    if (!File.Exists(path)) Assert.Inconclusive("Image not copied");
    var picture = Image.FromFile(path);
 
    //create a new category with picture
    using (var work = new UnitOfWork())
    {
        var cat = new Category();
        cat.CategoryName = "Test Category";
        cat.Description = "Test Category with Image";
        cat.Picture = new Picture(picture, ImageFormat.Jpeg);
 
        CategoryRepository.SaveOrUpdate(cat);
        work.Commit(); //physically save it to sql
 
        categoryId = cat.Id;
    }
 
    //reload the category
    using (var work = new UnitOfWork())
    {
        var cat = CategoryRepository.GetById(categoryId);
        Assert.IsNotNull(cat);
 
        var img = cat.Picture.Image;
        //if (img != null)
        //{
        //    var fs = new FileStream(Path.Combine(Environment.CurrentDirectory, "Windows XP2.jpg"), FileMode.Create);
        //    img.Save(fs, cat.Picture.ImageFormat);
        //}
        Assert.AreEqual(picture.Width, img.Width);
 
        CategoryRepository.Delete(cat); //delete it
        work.Commit();
    }
}