Most applications contain some set of static relational data, such as a list of countries, states, credit card types, and others. The application doesn't need to waste time retrieving this static data from the database. It never changes. In this recipe, I'll show you how we can use the well-known instance type from the Unofficial NHibernate AddIns project to avoid this unnecessary work.
WKITExample
and add a reference to NHibernate.dll
.GenericWellKnownInstanceType
class:[Serializable] public abstract class GenericWellKnownInstanceType<T, TId> : IUserType where T : class { private Func<T, TId, bool> findPredicate; private Func<T, TId> idGetter; private IEnumerable<T> repository; protected GenericWellKnownInstanceType( IEnumerable<T> repository, Func<T, TId, bool> findPredicate, Func<T, TId> idGetter) { this.repository = repository; this.findPredicate = findPredicate; this.idGetter = idGetter; } public Type ReturnedType { get { return typeof(T); } } public bool IsMutable { get { return false; } } public new bool Equals(object x, object y) { if (ReferenceEquals(x, y)) { return true; } if (ReferenceEquals(null, x) || ReferenceEquals(null, y)) { return false; } return x.Equals(y); } public int GetHashCode(object x) { return (x == null) ? 0 : x.GetHashCode(); } public object NullSafeGet(IDataReader rs, string[] names, object owner) { int index0 = rs.GetOrdinal(names[0]); if (rs.IsDBNull(index0)) { return null; } var value = (TId)rs.GetValue(index0); return repository.FirstOrDefault(x => findPredicate(x, value)); } public void NullSafeSet(IDbCommand cmd, object value, int index) { if (value == null) { ((IDbDataParameter)cmd.Parameters[index]) .Value = DBNull.Value; } else { ((IDbDataParameter)cmd.Parameters[index]) .Value = idGetter((T)value); } } 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; } /// <summary> /// The SQL types for the columns /// mapped by this type. /// </summary> public abstract SqlType[] SqlTypes { get; } }
StateType
class:public class StateType : GenericWellKnownInstanceType<State, string> { private static readonly SqlType[] sqlTypes = new[] { SqlTypeFactory.GetString(2)}; public StateType() : base(new States(), (entity, id) => entity.PostalCode == id, entity => entity.PostalCode) { } public override SqlType[] SqlTypes { get { return sqlTypes; } } }
State
class:[Serializable] public class State { public virtual string PostalCode { get; private set; } public virtual string Name { get; private set; } internal State(string postalCode, string name) { PostalCode = postalCode; Name = name; } }
States
collection class:public class States : ReadOnlyCollection<State> { public static State Arizona = new State("AZ", "Arizona"); public static State California = new State("CA", "California"); public static State Colorado = new State("CO", "Colorado"); public static State Oklahoma = new State("OK", "Oklahoma"); public static State NewMexico = new State("NM", "New Mexico"); public static State Nevada = new State("NV", "Nevada"); public static State Texas = new State("TX", "Texas"); public static State Utah = new State("UT", "Utah"); public States() : base(new State[] { Arizona, California, Colorado, Oklahoma, NewMexico, Nevada, Texas, Utah }) { } }
Address
class using the following properties:public virtual Guid Id { get; set; } public virtual string Line1 { get; set; } public virtual string Line2 { get; set; } public virtual string City { get; set; } public virtual State State { get; set; } public virtual string Zip { get; set; }
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="WKITExample" namespace="WKITExample"> <typedef class="WKITExample.StateType, WKITExample" name="State"/> <class name="Address"> <id name="Id"> <generator class="guid.comb" /> </id> <property name="Line1" not-null="true" /> <property name="Line2" /> <property name="City" not-null="true" /> <property name="State" type="State" not-null="true" /> <property name ="Zip" not-null="true" /> </class> </hibernate-mapping>
In this recipe, we have an Address
entity with a State
property. Suppose we have a requirement to print the state's postal abbreviation on shipping labels, but we need to display the full state name when the user completes an order. It would be a waste of resources to fetch these State
entities from the database each time.
GenericWellKnownInstanceType
allows us to create a static list of States
in our application, and use them with our Address
entity. We use the PostalCode
property to uniquely identify it in the list. In the database, this postal code value is stored in the State
field of Address
. When NHibernate loads an Address
from the database, it attaches the appropriate State
instance to the State
property. In this way, State
works just like an entity. This is handled by the StateType
class, which implements IUserType
. When loading an Address
, the StateType
class is responsible for reading the abbreviation from the raw data and returning the correct State
instance. Similarly, when we save an address, it translates the State
instance to the abbreviation stored in the Address
table.
When inheriting from GenericWellKnownInstanceType
, we must provide the following four items:
states
collection.The Unofficial NHibernate AddIns project also includes a WellKnownInstanceType
, which specifies a 32-bit integer database value.