A Quick Play With IBatis.NET
You may remember a few posts back that I was working with a team trying to use a pure stored procedure approach to access a database, and trying to do so using an OR/M. One of the commenters on the post mentioned iBATIS. Now I always thought iBATIS was a competitor to NHibernate, Linq2SQL, et al. but it's actually a different approach where instead of being a fully blown OR/M it just does simple data mapping of business objects to and from SQL statements and nothing more than that.
So I decided to have a bit of a play with it and I must say it looks pretty good. Here's a quick bit of sample code I knocked up to see how it works.
Assume I have the following sproc:
CREATE PROCEDURE InsertSaleHeader
@Tax money,
@TotalValue money,
@SaleNumber int output
AS
BEGIN
SET NOCOUNT ON;
INSERT INTO [dbo].[SaleHeader]
([Tax]
,[TotalValue]
,[ModifiedDate])
VALUES
(@Tax,@TotalValue,GETDATE())
SET @SaleNumber = scope_identity()
END
It's just a basic insert sproc that updates a modified date on insert and also returns a new identity value for the record we just added using an out parameter. This is the sort of thing that was a real pain to so with many of the full featured OR/M's.
I then wrote an integration test (p.s. please forgive my incorrect casing in places):
using IBatisNet.DataMapper;
using SalesDAL.ibatis;
namespace SalesIntegrationTests
{
[TestClass]
public class ibatisSaleRepositoryTests
{
[TestMethod]
public void CreateSaleAndGetSalesUsingIBatis()
{
ISqlMapper mapper = Mapper.Instance();
ISaleRepository repository = new ibatisSaleRepository(mapper);
ISale sale = new SalesTax.Sale();
sale.Add(new SalesTax.SaleLine(1, "imported box of chocolates", 10.00m, true));
bool result = repository.CreateSale(sale);
Assert.IsTrue(result);
}
}
}
This just creates an instance of a sale object and sends it to a sale repository class. The code for the ibatisSaleRepository is shown below.
namespace SalesDAL.ibatis
{
public class ibatisSaleRepository : ISaleRepository
{
private ISqlMapper mapper;
public ibatisSaleRepository(ISqlMapper mapper)
{
this.mapper = mapper;
}
public bool CreateSale(ISale sale)
{
mapper.Insert("InsertSaleHeader", sale);
LastSaleId = sale.SaleNumber;
return true;
}
public int LastSaleId
{
get;
private set;
}
As you can see the only actual work that happens is in the CreateSale() method where the IBatis Insert() method is called, passing in the object to be saved (note that I’m only saving the sale header here – we could extend this to save the sale lines easily enough).
And that’s it for the code. Nothing too complex at all. The rest of the work is done through the config files that IBatis loads during the Mapper.Instance() method call in the unit test. When it's initialised iBATIS loads up the SqlMap.config file and then processes it any other config files that are reference by it to create the mapping behaviours it needs to know.
The IBatis SqlMap.config file I used is shown here:
<?xml version="1.0" encoding="utf-8" ?>
<sqlMapConfig xmlns="http://ibatis.apache.org/dataMapper" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
<settings>
<setting useStatementNamespaces="false"/>
</settings>
<providers embedded="providers.config, SalesIntegrationTests" />
<database>
<provider name="sqlServer2.0"/>
<dataSource name="SalesData" connectionString="Data Source=.;Initial Catalog=SalesDatabase;Integrated Security=True"/>
</database>
<sqlMaps>
<sqlMap embedded="ibatis.SalesMap.xml, SalesDAL"/>
</sqlMaps>
</sqlMapConfig>
And finally the SalesMap.xml file is as follows:
<?xml version="1.0" encoding="UTF-8" ?>
<sqlMap namespace="SalesDAL" xmlns="http://ibatis.apache.org/mapping" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
<alias>
<typeAlias alias="SaleHeader" type="SalesInterfaces.ISale, SalesInterfaces" />
</alias>
<statements>
<procedure id="InsertSaleHeader" parameterMap="InsertSaleHeader-Params">
dbo.InsertSaleHeader
</procedure>
</statements>
<parameterMaps>
<parameterMap id="InsertSaleHeader-Params" class="SaleHeader">
<parameter property="Tax" />
<parameter property="TotalValue" />
<parameter property="SaleNumber" direction="Output" column="SaleNumber" />
</parameterMap>
</parameterMaps>
</sqlMap>
I won't go into all the details of the configuration, but as you can see it's not onerous in any way. In fact I found that overall iBATIS it’s a lot simpler to use than NHibernate for this type of operation and it's much easier and faster using it than doing the alternative and writing ADO.NET by hand.
Oh, I should also mention that there is a Castle Windsor facility that loads iBatis up for you so you don’t have to maintain dependencies throughout your application.
So when is using IBatis a good choice? I'll let the IBatis team say it in their own words (taken from the documentation):
So, how do you decide whether to OR/M or to DataMap? As always, the best advice is to implement a representative part of your project using either approach, and then decide. But, in general, OR/M is a good thing when youLikewise, the best time to use a Data Mapper, like iBATIS, is when:
- Have complete control over your database implementation
- Do not have a Database Administrator or SQL guru on the team
- Need to model the problem domain outside the database as an object graph.
In the end, you have to decide what's best for your project. If a OR/M tool works better for you, that's great! If your next project has different needs, then we hope you give iBATIS another look. If iBATIS works for you now: Excellent!
- You do not have complete control over the database implementation, or want to continue to access a legacy database as it is being refactored.
- You have database administrators or SQL gurus on the team.
- The database is being used to model the problem domain, and the application's primary role is to help the client use the database model.