maandag 30 april 2007

NHibernate Session Management in Domain Driven App's

As I've posted earlier, Billy McCafferty has written an excellent article on using NHibernate within ASP.NET applications.
In this article, he describes how to use the 'Open Session in View' pattern. This is perfectly useable and a very clean way of working with NHibernate in a DDD ASP.NET application, unfortunately, it is -imho- less (or not) useable in a WinForms application.
The 'Open Session in View' pattern implies that a new NHibernate ISession object is created at each request, and closed at the end of each request. (Changes are committed as well).

It is very important that the Repositories keep their hands off of Transaction Management. In other words: a repository should not start, commit or rollback a transaction. This is natural, since the repository doesn't know anything of the 'context' of the Unit of Work. Therefore, transaction managment should be a responsability of the client (the UI, the Service layer, whatever).
To achieve this, Billy stores the current ISession in the CallContext or HttpContext (he has made abstraction of which implementation is used offcourse. CallContext is used in the case of a WinForms app or a Unit-Test project, HttpContext is used in case of an ASP.NET app; see the source-code that accompagnies the article for more details).
In this way, the repositories can easily access the current ISession when they need it by just getting it out of the CallContext or HttpContext, and the ASP.NET application (the ASP.NET webpages) have access to the ISession as well, so they can start, commit and rollback the unit-of-work.
This is the ideal solution in ASP.NET, since you only need one 'ISession' on each request. At the end of the request, the Session can be closed (committed / rollbacked), so they're short-lived, and this is perfect.

However, in a WinForms application, you'll probably want more then one 'Unit of Work' at the same time, and therefore, have more then one open ISession at the same time.
An example will clarify this a bit more:

Suppose you've a WinForms application which consists of a form which lists all Customers. You can edit a customer by double-clicking it; a 'Customer-Detail' form will open up, where you can make changes to the Customer.

Now, what happens if you edit a Customer ? You'll retrieve the ISession from the CallContext, load the Customer from the DB, make some changes and confirm the changes; closing the form will close the ISession.
So far, no problem. However, what happens if you open another Customer-Detail form before you've committed the changes to the first Customer ?
What if you have 2 Customer Detail Forms open, make changes to Customer 1, save the changes and close the form. Afterwards, you want to save the changes to Customer 1... The problem now is that you've closed the ISession when you've closed the first Customer-Detail form. Customer 2 was attached to the same session which is now closed. If you want to save the changes to customer 2, you'll need an ISession, so you'll require a new ISEssion in this case. Then, NHibernate will think that Customer 2 is a new Customer, and will try to insert a new record to the DB where in fact, the existing Customer needs to be updated.

In my opinion, it is better to have a seperate Unit Of Work (ISession) for each instance of the CustomerDetail form in this case.
This implies that you should create a new ISession for each instance of the CustomerDetail form: this means that the ISession cannot be kept in the CallContext since otherwise, each form would share the same ISession.
Then... how should this be solved ? In my humble opinion, the best way to solve this is to create an ISession when an instance of a form is created, pass it to the repositories, and use it to handle the transaction(s) in the form:

public class CustomerDetailForm : Form
{
private ISession _unitOfWork;

private CustomerRepository _custRep;

private Customer _customer;

public void ShowCustomer( int customerId )
{
_unitOfWork = AppSettings.Instance.SessionFactoryObj.OpenSession();

_custRep = new CustomerRepository(_unitOfWork);

_customer = _custRep.GetCustomer(customerId);

// Disconnect from the DB, since we do not need DB action at this time
// anymore.
_unitOfWork.Disconnect();

}

public void OKButton_Click(object sender, EventArgs e )
{
// Reconnect to the DB
_unitOfWork.Reconnect();

ITransaction tx = _unitOfWork.BeginTransaction();
_custRep.Save (_customer);
tx.Commit();
}
}

I don't know if there's any better / cleaner way of doing this... If you know one, please let me know. :) It is offcourse cleaner if you could abstract the use of NHibernate's ISession by creating a class which encapsulates the ISession, but I wanted to keep things a bit sparse. :)

vrijdag 27 april 2007

Article: NHibernate best practices with ASP.NET

Billy McCafferty has written an excellent article on NHibernate and Domain Driven Design. It can be found here on the CodeProject.
Although it is focused on using NHibernate in ASP.NET, it is still very interesting even if you do not develop in ASP.NET. He talks about a few key concepts in DDD, and the example code is also very interesting.
There's one little drawback however in his example code: although he mentions that the way of working he demonstrates should work for Winforms development as well, I do not completely agree on that. However, I've to investigate it a bit more, which I'll do as soon as I find some time. Once done, I'll post my findings offcourse.

zondag 22 april 2007

Split

When I started this weblog in december 2005, I decided to blog about software development and about photography.

Now, 1 and a half year later, it seems to me that this was not such a good idea; I feel that software engineering and photography do not share the same audience.
Now, you probably think, did it really take you that long to acknowledge this fact ? Well, not really: during the first few months of blogging, the number of photography posts here was pretty low. At this time, I've noticed that 3 out of the 5 last posts were tagged with 'photography', so that's why I've finally decided to split things up.

This will remain my weblog that is focussed on programming (notice that I've changed the name from 'weblog' to 'devlog' as well :) ). Things that are focused on photography will appear on my photolog, so if you're interested, you can have a look there :).

I've choosen Wordpress to host my photo-blog, since Wordpress allows me to import posts from blogger, and that's what I've done: I've imported all the 'photography-tagged' posts from this weblog in my wordpress photo-blog, and translated them to dutch.

vrijdag 13 april 2007

.NET 1.1: Problems when selecting rows from a DataTable using an aggregate filter

I came accross a rather weird error today when I was working in C# 1.1. I was trying to select the records that exist in a DataTable that had more then one child in a related datatable.


To make things a little bit more clear, I've reproduced this by creating a Typed Dataset which contains 2 tables from the Northwind database:



What I want to do, is to select the Categories for which there are multiple products classified into.
So, this is what I've done; I created an instance of this dataset, and populated it with some data:

NorthwindDS ds = new NorthwindDS();
NorthwindDS.CategoriesRow c1 = ds.Categories.NewCategoriesRow();
c1.CategoryID = 1;
c1.CategoryName = "Category 1";
ds.Categories.AddCategoriesRow (c1);

NorthwindDS.CategoriesRow c2 = ds.Categories.NewCategoriesRow();
c2.CategoryID = 2;
c2.CategoryName = "Category 2";
ds.Categories.AddCategoriesRow (c2);

NorthwindDS.ProductsRow p1 = ds.Products.NewProductsRow();
p1.ProductID = 1;
p1.CategoryID = 1;
p1.ProductName = "Product 1";
ds.Products.AddProductsRow (p1);

NorthwindDS.ProductsRow p2 = ds.Products.NewProductsRow();
p2.ProductID = 2;
p2.CategoryID = 1;
p2.ProductName = "Product 2";
ds.Products.AddProductsRow (p2);

// The following line is important to illustrate the problem :)
ds.AcceptChanges();

This is pretty simple, I've just populated the Dataset with 2 categories and 2 products, and the 2 products are both linked to Category 1.
Now, the Query I've talked about earlier should thus return 1 Category, specifically, Category 1, since there's more then one product related to this category.
I do this using the following code:

string filterExpression = "COUNT (Child.CategoryID) > 1";
DataRow[] dr = ds.Categories.Select (filterExpression);

foreach( NorthwindDS.Category cat in dr )
{
Console.WriteLine (cat.CategoryName);
}

This gives me the expected result, it writes 'Category 1' to the output-window.
However, suppose that I've populated this Dataset with data coming from the Database, and that I add a product - record to this Dataset after the Dataset has been populated...
I can simulate this by adding a ProductRow to the DataSet after the 'AcceptChanges()' call:


// previous lines which create the dataset, and add the categories
// and the first 2 products are left out for brevity
ds.AcceptChanges();

NorthwindDS.ProductsRow p3 = ds.Products.NewProductsRow();
p2.ProductID = 3;
p2.CategoryID = 1;
p2.ProductName = "Product 3";
ds.Products.AddProductsRow (p3);

filterExpression = "COUNT (Child.CategoryID) > 1";
DataRow[] dr = ds.Categories.Select (filterExpression);

foreach( NorthwindDS.Category cat in dr )
{
Console.WriteLine (cat.CategoryName);
}

Now, I would expect that this gives me the exact same result as before, however, I get an exception:

An unhandled exception of type 'System.Data.VersionNotFoundException' occurred in system.data.dll

Addition information: There is no original data to access.

Probably, .NET wants to access the original contents of my new ProductRow, but since it is a new one, it has no Original DataViewRowState ...
I've tried to work around this by specifying that the CurrentRows should be used:

ds.Categories.Select (filterExpression, 
string.Empty,
DataViewRowState.CurrentRows);

But, to no avail. (Which didn't surprise me, because this line just says: return the Current DataViewRowState of the Categories and the problem seems to occur when the Product datarows are accessed).

So, this seems to be a bug in the .NET 1.1 (I've tried this in .NET 2.0 as well, and there, it works like a charm).

So, is there anybody who knows how to solve or work around this issue ?

zondag 1 april 2007

Tagliatelle - scampi

Tagliatelle - Scampi