Статьи

Понимание системы типов NHibernate

Эта статья взята из NHibernate в действии от Manning Publications. Эта статья углубляется в систему типов NHibernate. Оглавление, форум авторов и другие ресурсы можно найти по адресу http://www.manning.com/kuate/ .

Это воспроизводится здесь с разрешения Manning Publications . Книги Мэннинга продаются исключительно через Мэннинга. Посетите страницу книги для получения дополнительной информации.

Мягкая печать: февраль 2009 | 400 страниц
ISBN: 1932394923

 

Используйте код «dzone30», чтобы получить 30% скидку на любую версию этой книги.

 

Сущности — это грубые классы в системе. Вы обычно определяете функции системы в терминах участвующих субъектов: «пользователь делает ставку за элемент» — это типичное определение функции, в котором упоминаются три объекта — пользователь, ставка и элемент. Напротив, типы значений являются гораздо более детализированными классами в системе, такими как строки, числа, даты и денежные суммы. Эти мелкозернистые классы могут использоваться во многих местах и ​​служат многим целям; строка типа значения может хранить адрес электронной почты, имена пользователей и многое другое. Строки — это простые типы значений, но возможно (но не так часто) создавать более сложные типы значений. Например, тип значения может содержать несколько полей, например адрес.

Так как же мы различаем типы значений и сущности? С более формальной точки зрения, мы говорим, что сущность — это любой класс, экземпляры которого имеют собственную постоянную идентичность, а тип значения — это класс, экземпляры которого не имеют. Следовательно, экземпляры сущности могут находиться в любом из трех постоянных состояний жизненного цикла: временное, отсоединенное или постоянное. Однако мы не считаем, что эти состояния жизненного цикла применяются к более простым экземплярам типов значений. Кроме того, поскольку у сущностей есть собственный жизненный цикл, методы Save () и Delete () интерфейса NHibernate ISession будут применяться к ним, но не к экземплярам типа value. Для иллюстрации рассмотрим рисунок А.

Рисунок A — Объект заказа с типом значения TotalAmount

TotalAmount является экземпляром типа значения Money. Поскольку типы значений полностью связаны с принадлежащими им сущностями, TotalAmount сохраняется только при сохранении ордера.

Ассоциации и типы значений

Как мы уже говорили, не все типы значений просты. Типы значений также могут определять ассоциации. Например, наш тип значения Money может иметь свойство Currency, которое является ассоциацией с объектом Currency, как показано на рисунке 6.1.2.

Рисунок B — Тип значения Money с привязкой к объекту Currency.

Если у ваших типов значений есть ассоциации, они всегда должны указывать на сущности. Причина в том, что, если эти ассоциации могут указывать от сущностей на типы значений, тип значения может потенциально принадлежать нескольким сущностям, что нежелательно. Это одна из замечательных особенностей типов значений; если вы обновляете экземпляр типа значения, вы знаете, что он влияет только на объект, которому он принадлежит. Например, изменение TotalAmount одного заказа просто не может случайно повлиять на другие. До сих пор мы говорили о типах значений и сущностях с объектно-ориентированной точки зрения. Чтобы построить более полную картину, теперь мы рассмотрим, как реляционная модель видит типы и сущности значений и как NHibernate устраняет разрыв.

Мост от объектов к базе данных

Возможно, вы знаете, что архитектор базы данных увидит мир типов значений и сущностей несколько иначе, чем этот объектно-ориентированный взгляд на вещи. В базе данных таблицы представляют сущности, а столбцы представляют значения. Даже таблицы соединений и таблицы поиска являются сущностями. Итак, если все таблицы представляют сущности в базе данных, означает ли это, что мы должны сопоставить все таблицы сущностям в нашей доменной модели .NET? Как насчет тех типов значений, которые мы хотели в нашей модели? NHibernate предоставляет конструкции для решения этой проблемы. Например, сопоставление сопоставления «многие ко многим» скрывает промежуточную таблицу сопоставлений от приложения, поэтому мы не получаем нежелательную сущность в нашей модели предметной области. Аналогично, коллекция строк со значениями ведет себя как тип значений с точки зрения.Модель домена NET, даже если она сопоставлена ​​с собственной таблицей в базе данных.

Эти функции имеют свое применение и часто могут упростить ваш код на C #. Однако со временем мы стали подозревать их; эти «скрытые» сущности часто оказываются нужными в наших приложениях по мере развития бизнес-требований. Например, в таблице сопоставления «многие ко многим» часто добавляются дополнительные столбцы по мере созревания приложения, поэтому сама связь становится сущностью. Вы не ошибетесь, если сделаете каждый объект уровня базы данных открытым для приложения в качестве класса объекта. Например, мы склонны моделировать ассоциацию «многие ко многим» как две ассоциации «один ко многим» с промежуточным классом сущностей. Мы оставим вам окончательное решение и вернемся к теме ассоциаций «многие ко многим» позже в этой главе.

Типы картографирования

До сих пор мы обсуждали различия между типами значений и сущностями, как видно с точки зрения объектно-ориентированной и реляционной базы данных. Мы знаем, что сопоставление сущностей довольно просто — классы сущностей просто всегда сопоставляются с таблицами базы данных, используя элементы сопоставления <class>, <subclass> и <join-subclass>.

Типы значений нуждаются в чем-то большем, и именно здесь типы картинок входят в рисунок. Рассмотрим это сопоставление пользователя CaveatEmptor и адреса электронной почты:

<property
name="Email"
column="EMAIL"
type="String"/>

В ORM вам нужно беспокоиться как о типах .NET, так и о типах данных SQL. В приведенном выше примере представьте, что поле Email — это строка .NET, а столбец EMAIL — SQL-файл. Мы хотим сообщить NHibernate, как выполнить это преобразование, в которое входят типы отображения NHibernate. В этом случае мы указали тип отображения «String», который, как мы знаем, подходит для этого конкретного преобразования.

Тип отображения String — не единственный, встроенный в NHibernate; NHibernate поставляется с различными типами отображения, которые определяют стратегии сохранения по умолчанию для примитивных типов .NET и определенных классов, таких как DateTime.

Встроенные типы карт

Встроенные типы сопоставления NHibernate обычно отражают имя сопоставляемого им типа .NET. Иногда у вас есть выбор типов отображения, доступных для сопоставления определенного типа .NET с базой данных. Однако встроенные типы сопоставления не предназначены для выполнения произвольных преобразований, таких как сопоставление значения поля VARCHAR со значением свойства .NET Int32. Если вам нужна такая функциональность, вам нужно определить свои собственные типы значений. Мы вернемся к этой теме чуть позже в этой главе.

Теперь мы обсудим основные типы; дата и время, объекты, крупные объекты и различные другие встроенные типы сопоставления и показывают, какие типы данных .NET и System.Data.DbType они обрабатывают. DbTypes используются для определения типов поставщиков данных (отсюда и типы данных SQL).

Примитивные типы отображения .NET

Основные типы отображения в таблице A отображают типы примитивов .NET на соответствующие типы DbTypes.

Таблица A Примитивные типы

Тип отображения .NET Type System.Data.DbType
 Int16  System.Int16  DbType.Int16
 Int32  System.Int32  DbType.Int32
 Int64  System.Int64  DbType.Int64
 Один  System.Single  DbType.Single
 двойной  System.Double  DbType.Double
 Десятичный  System.Decimal  DbType.Decimal
 Байт  System.Byte  DbType.Byte
 голец  System.Char  DbType.StringFixedLength — 1 символ
 AnsiChar  System.Char  DbType.AnsiStringFixedLength — 1 символ
 логический  System.Boolean  DbType.Boolean
 Guid  System.Guid  DbType.Guid
 PersistentEnum  System.Enum (перечисление)  DbType для базового значения
 TrueFalse  System.Boolean  DbType.AnsiStringFixedLength — либо «T», либо «F»
 Да нет  System.Boolean  DbType.AnsiStringFixedLength — «Y» или «N»

You’ve probably noticed that your database doesn’t support some of the DbTypes listed in table A. However, ADO.NET provides a partial abstraction of vendor-specific SQL data types, allowing NHibernate to work with ANSI-standard types when executing data manipulation language (DML). For database-specific DDL generation, NHibernate translates from the ANSI-standard type to an appropriate vendor-specific type, using the built-in support for specific SQL dialects. (You usually don’t have to worry about SQL data types if you’re using NHibernate for data access and data schema definition.)

NHibernate supports a number of mapping types coming from Hibernate for compatibility (useful for those coming over from Hibernate or using Hibernate tools to generate hbm.xml files). Table B lists the additional names of NHibernate mapping types.

Table B Additional names of NHibernate mapping types

 Mapping type
 Additional name
 Binary  binary
 Boolean  boolean
 Byte  byte
 Character  character
 CultureInfo  locale
 DateTime  datetime
 Decimal  big_decimal
 Double  double
 Guid  guid
 Int16  short
 Int32  int
 Int32  integer
 Int64  long
 Single  float
 String  string
 TrueFalse  true_false
 Type  class
 YesNo  yes_no

From this table, you can see that writing type=»integer» or type=»int» is identical to type=»Int32″. Note that this table contains many mapping types that will be discussed in the following sections.

Date/time mapping types

Table C lists NHibernate types associated with dates, times, and timestamps. In your domain model, you may choose to represent date and time data using either System.DateTime or System.TimeSpan. As they have different purposes, the choice should be easy.

Table C Date and time typesExcerptOpenSourceSOAch5-6.doc

Mapping Type .NET Type System.Data.DbType
 DateTime  System.DateTime  DbType.DateTime — ignores the milliseconds
 Ticks  System.DateTime  DbType.Int64
 TimeSpan  System.TimeSpan  DbType.Int64
 Timestamp  System.DateTime  DbType.DateTime — as specific as database supports

Object mapping types

All .NET types in tables A and C are value types (i.e. derived from System.ValueType). This means that
they can’t be null; unless you use the .NET 2.0 Nullable<T> structure or the Nullables add-in, as
discussed in the next section. Table D lists NHibernate types for handling .NET types derived from
System.Object (which can store null values).

Table D Nullable object typesExcerptOpenSourceSOAch5-6.doc

Mapping Type  .NET Type  System.Data.DbType
 String  System.String  DbType.String
 AnsiString  System.String  DbType.AnsiString

This table is completed by tables E and F which also contain nullable mapping types.

Large object mapping types

Table E lists NHibernate types for handling binary data and large objects. Note that none of these types may be used as the type of an identifier property.

Table E Binary and large object typesExcerptOpenSourceSOAch5-6.doc

Mapping Type
.NET Type
 System.Data.DbType
 Binary  System.Byte[]  DbType.Binary
 BinaryBlob  System.Byte[]  DbType.Binary
 StringBlob  System.String  DbType.String
 Serializable  Any System.Object marked with SerializableAttribute  DbType.Binary

BinaryBlob and StringClob are mainly supported by SQL Server. They can have a very large size and are fully loaded in memory. This can be a performance killer if used to store very large objects. So use this feature carefully. Note that you must set the NHibernate property «prepare_sql» to «true» to enable this feature.

You can find up-to-date design patterns and tips for large object usage on the NHibernate website.

Various CLR mapping types

Table F lists NHibernate types for various other types of the CLR that may be represented as DbType.Strings in the database.

Table F Other CLR-related typesExcerptOpenSourceSOAch5-6.doc

Mapping Type
.NET Type
System.Data.DbType
 CultureInfo  System.Globalization.CultureInfo  DbType.String — 5 chars for culture
 Type  System.Type  DbType.String holding Assembly Qualified Name

Certainly, <property> isn’t the only NHibernate mapping element that has a type attribute.

Using mapping types

All of the basic mapping types may appear almost anywhere in the NHibernate mapping document, on normal property, identifier property, and other mapping elements.

The <id>, <property>, <version>, <discriminator>, <index>, and <element> elements all define an attribute named type. (There are certain limitations on which mapping basic types may function as an identifier or discriminator type, however.)

You can see how useful the built-in mapping types are in this mapping for the BillingDetails class:

<class name="BillingDetails"
table="BILLING_DETAILS"
lazy="false"
discriminator-value="0">

<id name="Id" type="Int32" column="BILLING_DETAILS_ID">
<generator class="native"/>
</id>

<discriminator type="Char" column="TYPE"/>

<property name="Number" type="String"/>
...
</class>

The BillingDetails class is mapped as an entity. Its discriminator, id, and Number properties are value typed, and we use the built-in NHibernate mapping types to specify the conversion strategy.

It’s often not necessary to explicitly specify a built-in mapping type in the XML mapping document. For instance, if you have a property of .NET type System.String, NHibernate will discover this using reflection and select String by default. We can easily simplify the previous mapping example:

<class name="BillingDetails"
table="BILLING_DETAILS"
lazy="false"
discriminator-value="0">

<id name="Id" column="BILLING_DETAILS_ID">
<generator class="native"/>
</id>

<discriminator type="Char" column="TYPE"/>

<property name="Number"/>
....
</class>

For each of the built-in mapping types, a constant is defined by the class NHibernate. NHibernateUtil. For example, NHibernate.String represents the String mapping type. These constants are useful for query parameter binding, as discussed in more detail in chapter 8:

session.CreateQuery("from Item i where i.Description like :desc")
.SetParameter("desc", desc, NHibernate.String)
.List();

These constants are also useful for programmatic manipulation of the NHibernate mapping metamodel, as discussed in chapter 3.

Of course, NHibernate isn’t limited to the built-in mapping types; you can create your own custom mapping types for handling certain scenarios. We’ll take a look this next, and explain how the mapping type system is a central to NHibernates flexibility.

Creating custom mapping types

Object-oriented languages like C# make it easy to define new types by writing new classes. Indeed, this is a fundamental part of the definition of object orientation. If you were limited to the predefined built-in NHibernate mapping types when declaring properties of persistent classes, you’d lose much of C#’s expressiveness. Furthermore, your domain model implementation would be tightly coupled to the physical data model, since new type conversions would be impossible.

In order to avoid that, NHibernate provides a very powerful feature called custom mapping types. NHibernate provides two user-friendly interfaces that applications may use when defining new mapping types, the first being NHibernate.UserTypes.IUserType. IUserType is suitable for most simple cases and even for some more complex problems. Let’s use it in a simple scenario.

Our Bid class defines an Amount property and our Item class defines an InitialPrice property, both monetary values. So far, we’ve only used a simple System.Double to represent the value, mapped with Double to a single DbType.Double column.

Suppose we wanted to support multiple currencies in our auction application and that we had to refactor the existing domain model for this change. One way to implement this change would be to add new properties to Bid and Item: AmountCurrency and InitialPriceCurrency. We would then map these new properties to additional VARCHAR columns with the built-in String mapping type. Imagine if we had currency stored in 100 places, this would be lots of changes. We hope you never use this approach!

Creating an implementation of IUserType

Instead, we should create a MonetaryAmount class that encapsulates both currency and amount. This is a class of the domain model and doesn’t have any dependency on NHibernate interfaces:

[Serializable]
public class MonetaryAmount
{
private readonly double value;
private readonly string currency;

public MonetaryAmount(double value, string currency)
{
this.value = value;
this.currency = currency;
}

public double Value { get { return value; } }
public string Currency { get { return currency; } }

public override bool Equals(object obj) { ... }
public override int GetHashCode() { ... }
}

We’ve also made life simpler by making MonetaryAmount an immutable class, meaning it can’t be changed after it’s instantiated. We would have to implement Equals() and GetHashCode() to complete the class — but there is nothing special to consider here aside that they must be consistent, and GetHashCode() should return mostly unique numbers.

We will use this new MonetaryAmount to replace the Double, as defined on the InitialPrice property for Item. We would benefit by using this new class in other places, such as the Bid.Amount.

The next challenge is in mapping our new MonetaryAmount properties to the database. Suppose we’re working with a legacy database that contains all monetary amounts in USD. Our new class means our application code is no longer restricted to a single currency, but it will take time to get the changes done by the database team. Until this happens, we’d like to store just the Amount property of MonetaryAmount to the database. Because we can’t store the currency yet, we’ll convert all Amounts to USD before we save them, and from USD when we load them.

The first step to handling this is to tell NHibernate how to handle our Monetarymount type. To do this, we create a MonetaryAmountUserType class that implements the NHibernate interface IUserType. Our custom mapping type is shown in listing A.

Listing A Custom mapping type for monetary amounts in USD

using System;
using System.Data;
using NHibernate.UserTypes;

public class MonetaryAmountUserType : IUserType {

private static readonly NHibernate.SqlTypes.SqlType[] SQL_TYPES =
{ NHibernateUtil.Double.SqlType };

public NHibernate.SqlTypes.SqlType[] SqlTypes { |1
get { return SQL_TYPES; }
}
public Type ReturnedType { get { return typeof(MonetaryAmount); } } |2

public new bool Equals( object x, object y ) { |3
if ( object.ReferenceEquals(x,y) ) return true;
if (x == null || y == null) return false;
return x.Equals(y);
}
public object DeepCopy(object value) { return value; } |4

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

public object NullSafeGet(IDataReader dr, string[] names, object owner){ |6
object obj = NHibernateUtil.Double.NullSafeGet(dr, names[0]);
if ( obj==null ) return null;
double valueInUSD = (double) obj;
return new MonetaryAmount(valueInUSD, "USD");
}

public void NullSafeSet(IDbCommand cmd, object obj, int index) { |7
if (obj == null) {
((IDataParameter)cmd.Parameters[index]).Value = DBNull.Value;
} else {
MonetaryAmount anyCurrency = (MonetaryAmount)obj;
MonetaryAmount amountInUSD = MonetaryAmount.Convert( anyCurrency, "USD" );
((IDataParameter)cmd.Parameters[index]).Value = amountInUSD.Value;
}
}

public static MonetaryAmount Convert( MonetaryAmount m, string targetCurrency)
{
... |8
}
}

The SqlTypes property tells NHibernate what SQL column types to use for DDL schema generation, as seen in #1. The types are subclasses of NHibernate.SqlTypes.SqlType. Notice that this property returns an array of types. An implementation of IUserType may map a single property to multiple columns, but our legacy data model only has a single Double.

In #2, we can see that ReturnedType tells NHibernate what .NET type is mapped by this IUserType.

The IUserType is responsible for dirty-checking property values (#3). The Equals() method compares the current property value to a previous snapshot and determines whether the property is dirty and must by saved to the database.

The IUserType is also partially responsible for creating the snapshot in the first place, as shown in #4. Since MonetaryAmount is an immutable class, the DeepCopy() method returns its argument. In the case of a mutable type, it would need to return a copy of the argument to be used as the snapshot value. This method is also called when an instance of the type is written to or read from the second level cache.

NHibernate can make some minor performance optimizations for immutable types. The IsMutable (#5) property tells NHibernate that this type is immutable.

The NullSafeGet() method shown near #6 retrieves the property value from the ADO.NET IDataReader. You can also access the owner of the component if you need it for the conversion. All database values are in USD, so you have to convert the MonetaryAmount returned by this method before you show it to the user.

In #7, the NullSafeSet() method writes the property value to the ADO.NET IDbCommand. This method takes whatever currency is set and converts it to a simple Double USD value before saving.

Note that, for briefness, we haven’t provided a Convert function as shown in #8. If we were to implement it, it would have code that converts between various currencies.

Mapping the InitialPrice property of Item can be done as follows:

<property name="InitialPrice"
column="INITIAL_PRICE"
type="NHibernate.Auction.CustomTypes.MonetaryAmountUserType, NHibernate.Auction"/>

This is the simplest kind of transformation that an implementation of IUserType could perform. It takes a Value Type class and maps it to a single database column. Much more sophisticated things are possible; a custom mapping type could perform validation, it could read and write data to and from an Active Directory, or it could even retrieve persistent objects from a different NHibernate ISession for a different database. You’re limited mainly by your imagination and performance concerns!

In a perfect world, we’d prefer to represent both the amount and currency of our monetary amounts in the database, so we’re not limited to storing just USD. We could still use an IUserType for this, but it’s limited; If an IUserType is mapped with more than one property, we can’t use them our HQL or Criteria queries. The NHibernate query engine wouldn’t know anything about the individual properties of MonetaryAmount. You still access the properties in your C# code (MonetaryAmount is just a regular class of the domain model, after all), but not in NHibernate queries.

To allow for a custom value type with multiple properties that can be accessed in queries, we should use the ICompositeUserType interface. This interface exposes the properties of our MonetaryAmount to NHibernate.

Creating an implementation of ICompositeUserType

To demonstrate the flexibility of custom mapping types, we won’t have to change our MonetaryAmount domain model class at all—we change only the custom mapping type, as shown in listing B.

Listing B Custom mapping type for monetary amounts in new database schemas

using System;
using System.Data;
using NHibernate.UserTypes;

public class MonetaryAmountCompositeUserType : ICompositeUserType {

public Type ReturnedClass { get { return typeof(MonetaryAmount); } }
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 object DeepCopy(object value) { return value; }
public bool IsMutable { get { return false; } }

public object NullSafeGet(IDataReader dr, string[] names, NHibernate.Engine.ISessionImplementor session, object owner) {
object obj0 = NHibernateUtil.Double.NullSafeGet(dr, names[0]);
object obj1 = NHibernateUtil.String.NullSafeGet(dr, names[1]);
if ( obj0==null || obj1==null ) return null;
double value = (double) obj0;
string currency = (string) obj1;
return new MonetaryAmount(value, currency);
}

public void NullSafeSet(IDbCommand cmd, object obj, int index, NHibernate.Engine.ISessionImplementor session) {
if (obj == null) {
((IDataParameter)cmd.Parameters[index]).Value = DBNull.Value;
((IDataParameter)cmd.Parameters[index+1]).Value = DBNull.Value;
} else {
MonetaryAmount amount = (MonetaryAmount)obj;
((IDataParameter)cmd.Parameters[index]).Value = amount.Value;
((IDataParameter)cmd.Parameters[index+1]).Value = amount.Currency;
}
}

public string[] PropertyNames { |1
get { return new string[] { "Value", "Currency" }; }
}
public NHibernate.Type.IType[] PropertyTypes { |2
get { return new NHibernate.Type.IType[] {
NHibernateUtil.Double, NHibernateUtil.String }; }
}
public object GetPropertyValue(object component, int property) { |3
MonetaryAmount amount = (MonetaryAmount) component;
if (property == 0)
return amount.Value;
else
return amount.Currency;
}
public void SetPropertyValue(object comp, int property, object value) { |4
throw new Exception("Immutable!");
}
public object Assemble(object cached, |5
NHibernate.Engine.ISessionImplementor session, object owner) {
return cached;
}
public object Disassemble(object value, |6
NHibernate.Engine.ISessionImplementor session) {
return value;
}
}

#1 shows how an implementation of ICompositeUserType has its own properties, defined by PropertyNames.

Similarly, the properties each have their own type, as defined by PropertyTypes (#2).

The GetPropertyValue() method, shown in #3, returns the value of an individual property of the MonetaryAmount.

Since MonetaryAmount is immutable, we can’t set property values individually (see #4) This isn’t a problem because this method is optional anyway.

In #5, the Assemble() method is called when an instance of the type is read from the second-level cache.

The Disassemble() method is called when an instance of the type is written to the second-level cache, as shown in #6.

The order of properties must be the same in the PropertyNames, PropertyTypes, and GetPropertyValues() methods. The InitialPrice property now maps to two columns, so we declare both in the mapping file. The first column stores the value; the second stores the currency of the MonetaryAmount. Note that the order of columns must match the order of properties in your type implementation:

<property name="InitialPrice" type="NHibernate.Auction.CustomTypes.MonetaryAmountCompositeUserType, NHibernate.Auction">
<column name="INITIAL_PRICE"/>
<column name="INITIAL_PRICE_CURRENCY"/>
</property>

In a query, we can now refer to the Amount and Currency properties of the custom type, even though they don’t appear anywhere in the mapping document as individual properties:

from Item i
where i.InitialPrice.Value > 100.0
and i.InitialPrice.Currency = 'XAF'

In this example we’ve expanded the buffer between the .NET object model and the SQL database schema with our custom composite type. Both representations can now handle changes more robustly.

If implementing custom types seems complex, relax; you rarely need to use a custom mapping type. An alternative way to represent the MonetaryAmount class is to use a component mapping, as in section 4.4.2, “Using components.” The decision to use a custom mapping type is often a matter of taste.

There are few more interfaces that can be used to implement custom types; they are introduced in
the next section.

Other interfaces to create custom mapping types

You may find that the interfaces IUserType and ICompositeUserType do not allow you to easily add
more features to your custom types. In this case, you will need to use some of the other interfaces
which are in the NHibernate.UserTypes namespace:

The IParameterizedType interface allows you to supply parameters to your custom type in the
mapping file. This interface has a unique method: SetParameterValues(IDictionary parameters)
that will be called at the initialization of your type. Here is an example of mapping providing a
parameter:

<property name="Price">
<type name="NHibernate.Auction.CustomTypes.MonetaryAmountUserType">
<param name="DefaultCurrency">Euro</param>
</type>
</property>

This mapping tells the custom type to use Euro as currency if it isn’t specified.

The IEnhancedUserType interface makes it possible to implement a custom type that can be marshalled to and from its string representation. This functionality allows this type to be used as identifier or discriminator type. To create a type that can be used as version, you must implement the IUserVersionType interface.

The INullableUserType interface allows you to interpret non-null values in a property as null in the database. When using dynamic-insert or dynamic-update, fields identified as null will not be inserted or updated. This information may also be used when generating the where clause of the SQL command when optimistic locking is enabled.

The last interface is different from the previous because it is meant to implement user defined collection types: IUserCollectionType. For more details, take a look at the implementation NHibernate.Test.UserCollection.MyListType in the source code of NHibernate.

Now, let’s look at an extremely important application of custom mapping types. Nullable types are found in almost all enterprise applications.

Using Nullable types

With .NET 1.1, primitive types can not be null; but this is no longer the case in .NET 2.0. Let’s say that we want to add a DismissDate to the class User. As long as a user is active, its DismissDate should be null. But the System.DateTime struct can not be null. And we don’t want to use some «magic» value to represent the null state. With .NET 2.0 (and 3.5 of course), you can simply write:

public class User
{
...
private DateTime? dismissDate;
public DateTime? DismissDate
{
get { return dismissDate; }
set { dismissDate = value; }
}
...
}

We omit other properties and methods because we focus on the nullable property. And no change is required in the mapping.

If you work with .NET 1.1, the Nullables add-in (in the NHibernateContrib package for versions prior to NHibernate 1.2.0) contains a number of custom mapping types which allow primitive types to be null. For our previous case, we can use the Nullables.NullableDateTime class:

using Nullables;

[Class]
public class User {
...
private NullableDateTime dismissDate;

[Property]
public NullableDateTime DismissDate
{
get { return dismissDate; }
set { dismissDate = value; }
}
...
}

The mapping is quite straightforward:

<class name="Example.Person, Example">
...
<property name="DateOfBirth" type="Nullables.NHibernate.NullableDateTimeType, Nullables.NHibernate" />
</class>

It is important to note that, in the mapping, the type of DismissDate must be Nullables.NHibernate.NullableDateTimeType (from the file Nullables.NHibernate.dll). This type is a wrapper used to translate Nullables types from/to database types. But if when using the NHibernate.Mapping.Attributes library, this operation is automatic, that’s why we just had to put the attribute [Property].

The NullableDateTime type behaves exactly like System.DateTime; there are even implicit operators to easily interact with it. The Nullables library contains nullable types for most .NET primitive types supported by NHibernate. You can find more details in NHibernate documentation.

Using enumerated types

An enumeration (enum) is a special form of value type, which inherits from System.Enum and supplies alternate names for the values of an underlying primitive type.

For example, the Comment class defines a Rating. If you recall, in our CaveatEmptor application, users are able to give other comments about other users. Instead of using a simple int property for the rating, we create an enumeration:

public enum Rating {
Excellent,
Ok,
Low
}

We then use this type for the Rating property of our Comment class. In the database, ratings would be represented as the type of the underlying value. In this case (and by default), it is Int32. And that’s all we have to do. We may specify type=»Rating» in our mapping, but it is optional; NHibernate can use reflection to find this.

One problem you might run into is using enumerations in NHibernate queries. Consider the following query in HQL that retrieves all comments rated “Low”:

IQuery q = session.CreateQuery("from Comment c where c.Rating = Rating.Low");

This query doesn’t work, because NHibernate doesn’t know what to do with Rating.Low and will try to use it as a literal. We have to use a bind parameter and set the rating value for the comparison dynamically (which is what we need for other reasons most of the time):

IQuery q = session.CreateQuery("from Comment c where c.Rating = :rating");
q.SetParameter("rating", Rating.Low, NHibernateUtil.Enum(typeof(Rating));

The last line in this example uses the static helper method NHibernateUtil.Enum() to define the NHibernate Type, a simple way to tell NHibernate about our enumeration mapping and how to deal with the Rating.Low value.

We’ve now discussed all kinds of NHibernate mapping types: built-in mapping types, user-defined custom types, and even components. They’re all considered value types, because they map objects of value type (not entities) to the database. With a good understanding of what value types are, and how they are mapped, you can now move on to the more complex issue of collections of value typed instances.