User-defined types

For every property on the entity that you map, NHibernate uses its own defaults to determine the right database column type. On most occasions, this just works. If it does not and you need minor changes to type, for example, instead of Int32 you want Int64, you can do it via entity mapping. But if you need to map a database column to a property of a completely unrelated type, then you can define your own type for NHibernate to work with. This obviously assumes that your code knows both the type in the database and the type in the code. Your code also should know how these two types convert from each other.

Consider for example, you are working on a legacy database situation. You have got an Employee table which has a NVARCHAR(1) type of column named IsEmployed. A value of Y in this column means that the employee is still part of the organization. A value of N in this column means that the employee has left the organization. Now, while building the domain model for this domain, you may not want to use a string type to designate whether the employee is still part of the organization or not. You would rather use a Boolean. This conversion from a string type to Boolean is something NHibernate cannot do out of the box and we would need to define a type that can do the conversion for us. Let's see how we can handle this situation using a user-defined type.

Tip

Situations like this, where you use a Boolean type on the application side and best suitable type on the database side, can also be handled using query substitutions. Query substitutions would let you use Boolean types in a database independent way. Readers are advised to explore this option before making the final choice.

A user-defined type is any class that implements the NHibernate.UserTypes.IUserType interface. Once this class is defined, we can use it during mapping of a property to tell NHibernate that it should use this class whenever it is dealing with that particular property. Let's see how this works by implementing a type to handle conversion from string to Boolean type we just discussed. Following code listing shows a class YesNoType that implements IUserType:

public class YesNoType : IUserType
{
  public bool Equals(object x, object y)
  {
    if (ReferenceEquals(x, y))
    {
      return true;
    }

    if (x == null || y == null)
    {
      return true;
    }

    return x.Equals(y);
  }

  public int GetHashCode(object x)
  {
    return x.GetHashCode();
  }

  public object NullSafeGet(IDataReader rs, string[] names, object owner)
  {
    var value = rs[names[0]] as string;

    if (value == @"Y") return true;
    return false;
  }

  public void NullSafeSet(IDbCommand cmd, object value, int index)
  {
    var parameter = (DbParameter)cmd.Parameters[index];

    if (value == null)
    {
      parameter.Value = DBNull.Value;
    }
    else
    {
      var bValue = (bool) value;
      if (bValue)
      {
        parameter.Value = "Y";
      }
      else
      {
        parameter.Value = "N";
      }
    }
  }

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

  public object Replace(object original, object target, object owner)
  {
    return original;
  }

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

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

  public SqlType[] SqlTypes
  {
    get
    {
      return new[]
      {
        new SqlType(DbType.String),
      };
    }
  }

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

  public bool IsMutable
  {
    get { return false; }
  }
}

There is a lot of code in the above implementation. But on careful observation, you would realize that some of the methods are not new and are clearly needed for NHibernate to do its job.

Let's go over the important methods to understand their role in the process:

Method name

Purpose

NullSafeGet

This is where the conversion from the database type to domain type happens. The instance of IDataReader that holds the result set returned from the database is passed into this method. We can then retrieve the column we want to convert and run the custom conversion logic. You can see that here we return a true if IDataReader contains a string Y, otherwise we return false.

NullSafeSet

This is exactly opposite of NullSafeGet. This is on the way to the database. IDbCommand containing the details to build the result SQL is passed into this method. Here we update the value for the IsEmployed parameter after converting it using the custom conversion logic.

DeepCopy

Returns a deep copy of the persistent state. The persistent state is passed in as object. Persistent state in our case is a simple Boolean so that we return the passed object as is.

Replace

This method is used during Merge to replace the value of persistent entity with the value from detached entity. Recommended practice is to return the first parameter passed into this function in case of immutable objects, and return a (recursive) copy otherwise. Again, in our example, Boolean is a value type and hence we can return the first parameter without any issues.

Assemble

A cacheable representation is passed into this function and a reconstructed object is expected to be returned. NHibernate recommends that this method should return a deep copy in the least.

Disassemble

Exactly opposite of Assemble but goes with the same recommendation.

SqlTypes

An array of types on the database column side.

ReturnedType

Type of the property on the domain entity.

IsMutable

This tells whether the property is mutable.

Let's tell NHibernate to use this type. If I had a Boolean property named IsEmployed on the Employee entity, then following is how I would map it to make use of YesNoType:

public class EmployeeMappings : ClassMapping<Employee>
{
  public EmployeeMappings()
  {
    Property(e => e.IsEmployeed, mapper => mapper.Type<YesNoType>());
  }
}

Now you can use the IsEmployed property on the Employee entity as a normal Boolean property. Every time the property value needs to be updated into the database or needs to be read from the database, NHibernate would handle the conversion.

Note

In the beginning, when I said that NHibernate does not support the preceding conversion out-of-the-box, I lied. In reality, the example we used is so common that NHibernate has a built-in type to support this. It is called NHibernate.Type.YesNoType. The built-in type is not implemented as a user-defined type but does exactly the same thing. If you come across a similar situation then you can avoid implementing a user defined type by making use of this build-in type. There are several such built-types to support commonly occurring legacy database situations.

Other user-defined types

IUserType is the most basic form of a user defined type. NHibernate has defined interfaces to represent more specific user defined types. These types can be used instead of IUserType if the latter cannot handle the situation sufficiently. Following table describes these additional user defined types:

Type

Purpose

ICompositeUserType

A custom type which can have its own properties. These properties may be mapped to a different table and can be referenced while querying the parent entity. This gives an effect similar to components.

IUserCollectionType

A user-defined collection type.

IEnhancedUserType

A custom type that may function as an identifier or discriminator type, or may be marshalled to and from an XML document.

IParameterizedUserType

A parameterizable user-defined type. This lets you parameterize values for properties on the type through mapping. It is useful when the same type is used in multiple places with minor changes in its behavior. These changes can be driven by the values of the parameterized properties.

ILoggableUserType

Can be used by the user-defined types that want to perform custom logging of their corresponding values.

IUserVersionType

A user defined type that may be used for a version property.

Examples or detailed explanation of each of these types is beyond the scope of this book. If you ever need to use one of the above types, feel free to ask for a helping hand on NHibernate user group.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.21.248.162