Developing Dexter I encountered again a usual usual error you have to deal with NHibernate and strings: we tried to persist en entity whose string field exceeded the limit imposed by the database table; Nhibernate rightfully complained with:

System.Data.SqlClient.SqlException: String or binary data would be truncated. The statement has been terminated.

Given the fact that having partial data for these fields was acceptable we decided to truncate the value itself. Instead of using the usual solution - that is: take care of this in the entity class or somewhere else during the validation - I decided to build some custom UserType and let NHibernate take care of the truncation if needed (this way we do not have to change anything in the logic of the application).

Why this approach ? well digging into NHibernate code you can see that it uses a lot of structures very similar to a UserType to actually take care of the interaction between the mapped properties and the database (look at the NHibernate.Type namespace in the source code or with Reflector), adding another one isn’t a big issue so I decided to follow their approach and inherited directly from AbstractStringType:

public abstract class AbstractTruncatedStringType : AbstractStringType
{
	internal AbstractTruncatedStringType()
		: base(new StringSqlType())
	{
	}

	internal AbstractTruncatedStringType(StringSqlType sqlType)
		: base(sqlType)
	{
	}

	public abstract int Length { get; }

	public override void Set(System.Data.IDbCommand cmd, object value, int index)
	{
		string str = (string)value;
		if (str.Length > Length)
			str = str.Substring(0, Length);
		base.Set(cmd, str, index);
	}
}

and then added some specializations:

public class TruncatedString500 : AbstractTruncatedStringType
{
	public override int Length
	{
		get { return 500; }
	}

	public override string Name
	{
		get { return "TruncatedString500"; }
	}
}

public class TruncatedString100 : AbstractTruncatedStringType
{
	public override int Length
	{
		get { return 100; }
	}

	public override string Name
	{
		get { return "TruncatedString100"; }
	}
}

...

you can use these classes writing your mapping like this:

...
<class name="Data" table="DATA">
		<id name="Id" column="ID" type="guid" unsaved-value="00000000-0000-0000-0000-000000000000">
			<generator class="guid" />
		</id>
		<property name="Data1" column="DATA1" type="string" length="100" />
		<property name="TruncatedString" column="TruncatedString" length="10" type="Structura.NHibernate.UserTypes.TruncatedString10, Structura.NHibernate" />
		...

Which is good...but not enough for me...you see we need to implement a lot of different versions of this class based on the limit we want to impose to the string, why not make this type parametric then !? We can do it just implementing the IParameterizedType interface.

The code is quite straightforward to write:

public class TruncatedString : AbstractTruncatedStringType, IParameterizedType
{
	private const int DefaultLimit = 50;

	public override int Length
	{
		get { return _length; }
	}
	private int _length = DefaultLimit;

	public override string Name
	{
		get { return "TruncatedString"; }
	}

	public void SetParameterValues(System.Collections.Generic.IDictionary<string, string> parameters)
	{
		if (false == int.TryParse(parameters["length"], out _length))
			_length = DefaultLimit;
	}
}

And this is how you can use it in your mappings:

...
<property name="TruncatedString" column="TruncatedString" length="10">
	<type name="Structura.NHibernate.UserTypes.TruncatedString, Structura.NHibernate">
		<param name="length">10</param>
	</type>
</property>
...

Yet not perfect...but it’s an improvement, so you have two choices: implement multiple versions of the abstract class (and keep your mapping cleaner) or use the parameterized version (and have extra flexibility).

Related Content