IBatis.NET Tips & Tricks Part 2 – Type Conversions & Type Handlers
In this part of our IBatis.NET series, we’ll go over a couple of ways you can handle type mismatches between your database and your objects. We build on the code from Part 1 of this series, so start there if you want to be fully up to speed.
Automatic Conversions
The simplest (and possibly the only) example to demonstrate how an automatic conversion works would be when you have a varchar column in your database that is supposed to represent a boolean value, i.e. “true” or “false.” Looking through the AdventureWorks database, I couldn’t find any columns like this, so I made my own on the HumanResources.Department table.
I fill the column with the text “true” for all rows with id <= 9, “false” for the rest. This gives us the below dataset.
I create a really trivial Department object for purposes of our fun.
Then I create a really trivial map for this new object.
<select id="SelectAllDepartments" resultMap="DepartmentRM"> select * from HumanResources.Department </select> <resultMap id="DepartmentRM" class="Department"> <result property="TextualBoolean" column="TextualBoolean" dbType="varchar" type="bool"/> </resultMap>
Finally, I write a test to exercise my new stuff.
[Test]
public void ImplicitTypeConversionTest()
{
IList<Department> depts = Mappers.AWMapper.QueryForList<Department>("SelectAllDepartments", null);
Assert.That(depts, Is.Not.Null);
IEnumerable<Department> trues = from x in depts
where x.TextualBoolean
select x;
IEnumerable<Department> falses = from x in depts
where !x.TextualBoolean
select x;
Assert.That(trues, Is.Not.Null);
Assert.That(trues.Count(), Is.EqualTo(9));
Assert.That(falses, Is.Not.Null);
Assert.That(falses.Count(), Is.EqualTo(7));
}
Straightforward stuff.
Custom Type Conversions
The more interesting case, and one that can be shown with the default AdventureWorks database, is the case where we want to perform a custom type conversion based on a database column value. Let’s take the HumanResources.Employee table.
Notice the highlighted column. We have “M” for married and “S” for single. In my Employee object however, I represent this using an enumeration.
So I want to read the “M” or “S” from the database and create a corresponding Employee object with the correct enumeration set. Naively I write my map as below.
<select id="SelectAllEmployees"
resultMap ="EmployeeRM">
select
*
from
HumanResources.Employee
</select>
<resultMap id="EmployeeRM"
class="Employee">
<result property="MaritalStatus" column="MaritalStatus" />
</resultMap>
And I write my test as below.
[Test]
public void ExplicitTypeConversionTest()
{
IList<Employee> emps = Mappers.AWMapper.QueryForList<Employee>("SelectAllEmployees", null);
Assert.That(emps, Is.Not.Null);
}
However when I run this I get the following error.
We see that IBatis.NET is dutifully trying to convert the first value it finds to the enumeration type of the specified target property, but is failing. As a side note here, we could change our enumeration to read “S” and “M” instead, and the above map would work fine. The point of enumerations is to make code more readable however, so let’s do better than that. We’ll write a custom type handler.
Our first step here will be to define the class that will be handling our custom type conversion. We create a new class called MaritalStatusTypeHandlerCallback, and implement the ITypeHandlerCallback interface in the IBatisNet.DataMapper.TypeHandlers namespace.
using System;
using IBatisNet.DataMapper.TypeHandlers;
namespace AdventureWorksDAL
{
public class MaritalStatusTypeHandlerCallback : ITypeHandlerCallback
{
public object GetResult(IResultGetter getter)
{
throw new NotImplementedException();
}
public object NullValue
{
get { throw new NotImplementedException(); }
}
public void SetParameter(IParameterSetter setter, object parameter)
{
throw new NotImplementedException();
}
public object ValueOf(string s)
{
throw new NotImplementedException();
}
}
}
So we need to implement four methods, all logically named. For our immediate purposes, we only need to implement the GetResult method. Note the getter parameter; this is what will give us the raw value from the database. So to convert our “S” and “M” values, we implement GetResult as below.
public object GetResult(IResultGetter getter)
{
if (getter.Value != null && getter.Value != System.DBNull.Value)
{
switch ((string)getter.Value)
{
case "S":
return MaritalStatusEnum.Single;
case "M":
return MaritalStatusEnum.Married;
default:
throw new IBatisNet.DataMapper.Exceptions.DataMapperException(
string.Format("Unknown marital status of {0} encountered.",
getter.Value));
}
}
else
{
throw new IBatisNet.DataMapper.Exceptions.DataMapperException(
"Unexpected null value for marital status encountered.");
}
}
Next we have to tell IBatis.NET to use our new type converter in our map. First we must tell IBatis.NET about our new type handler as we tell it about any class we want to use in a map, namely via a typeAlias element. Then I need to tell my map to use the new type converter by using the typeHandler attribute on the result element.
<typeAlias alias="MaritalStatusTypeHandlerCallback"
type="AdventureWorksDAL.MaritalStatusTypeHandlerCallback,AdventureWorksDAL"/>
<result property="MaritalStatus" column="MaritalStatus"
typeHandler="MaritalStatusTypeHandlerCallback" />
When we re-run our test, sure enough, we get a pretty green light.
Next Steps
In the next part of this series we will show a couple of different ways to compose maps, as well as some null value handling, both inline and using the type handler approach above.
Absolutely fantastic article. As I am still in the process of evaluating the overall effectiveness and ease of implementation of iBatis.Net, your posts on the subject are illuminating and insightful.
Wtf are you barebonescoder? There is no link to info about yourself. Add a link to a bio, loser…