Freitag, 22. Juni 2007

How to use CSLA objects with NHibernate

Instead of using the ADO.NET data reader as in the reference implementation to fill a CSLA object with data one can as well use an O/R mapper tool like NHibernate and fetch the data through this object layer.

We have the following diagram


where Category, Product and ProductNote are the NHibernate data objects and ProductBO, ProductNotesBO and ProductNoteBO are the CSLA business objects.

The root CSLA object ProductBO

Typicall code might look as follows:

private void DataPortal_Fetch(Criteria criteria)
{
using (ISession session = SessionFactory.Current.OpenSession())
{
Product item = session.Get<Product>(criteria.ProductId);
_productId = item.ProductID;
_version = item.Version;
_productName = item.ProductName;
_categoryId = item.Category.CategoryID;


//******** Get Child Collection(s)
_notes = ProductNotesBO.GetProductNotesBO(item.Notes);
}
}


On the other hand we also have to add or update the data in the database. Code might look as follows

[Transactional(TransactionalTypes.TransactionScope)]
protected override void DataPortal_Insert()
{
InsertOrUpdate();
}


[Transactional(TransactionalTypes.TransactionScope)]
protected override void DataPortal_Update()
{
InsertOrUpdate();
}


private void InsertOrUpdate()
{
using (ISession session = SessionFactory.Current.OpenSession())
{
Product item = new Product();
item.ProductID = _productId;
item.Version = _version;
item.ProductName = _productName;


//***************** This is how to solve Lookup-Type relations
// (Category is a Lookup Table for Product related tasks)
item.Category = new Category();
item.Category.CategoryID = _categoryId;
item.Category.Version = 999;


//***************** Only update the item if needed (new or dirty)!
if (base.IsDirty)
{
session.SaveOrUpdate(item);


if (IsNew)
{
_productId = item.ProductID; // get new (generated) ID!
_version = item.Version; // and new Version number
}
MarkOld();
}


//***************** Update Child Collection(s)
_notes.Update(session, item);
session.Flush();
}
}


To delete an object from the database one might use code as follows

[Transactional(TransactionalTypes.TransactionScope)]
protected override void DataPortal_DeleteSelf()
{
DataPortal_Delete(new Criteria(_productId));
MarkNew();
}


[Transactional(TransactionalTypes.TransactionScope)]
private void DataPortal_Delete(Criteria criteria)
{
using (ISession session = SessionFactory.Current.OpenSession())
{
//*************** First delete children (if allowed!!!)
session.Delete("from ProductNote n where n.Product.ProductID=:id",
criteria.ProductId, NHibernateUtil.Int32);


//*************** And now delete the item itself!
session.Delete("from Product p where p.ProductID = :id",
criteria.ProductId, NHibernateUtil.Int32);


session.Flush();
}
}


Note that we are using the Transactional attribute to make the insert, update and delete operations atomic.

The CSLA child collection ProductNotesBO

The factory methods of the child collection

public static ProductNotesBO NewProductNotesBO()
{
return new ProductNotesBO();
}


public static ProductNotesBO GetProductNotesBO(
IEnumerable; list)
{
return new ProductNotesBO(list);
}


private ProductNotesBO()
{
MarkAsChild();
}


private ProductNotesBO(IEnumerable list)
{
Fetch(list);
MarkAsChild();
}

Code used to fetch data

private void Fetch(IEnumerable list)
{
RaiseListChangedEvents = false;


foreach (ProductNote item in list)
{
Add(ProductNoteBO.GetProductNoteBO(item));
}


RaiseListChangedEvents = true;
}


Code used to add, update and delete data

internal void Update(ISession session, Product parent)
{
RaiseListChangedEvents = false;


//------------------- Delete Removed Children ---------------------
if (DeletedList.Count > 0)
{
StringBuilder ids = new StringBuilder();
foreach (ProductNoteBO item in DeletedList)
{
if (ids.Length > 0) ids.Append(",");
ids.Append(item.ProductNoteId);
}
string sql = string.Format(
"from ProductNote n where n.ProductNoteID in ({0})",
ids);
session.Delete(sql);


DeletedList.Clear();
}


//------------------- Add or Update Children ----------------------
foreach (ProductNoteBO item in this)
if (item.IsNew)
item.Insert(session, parent);
else
item.Update(session, parent);


RaiseListChangedEvents = true;
}


The CSLA child object ProductNoteBO

The factory methods of the child business object

public static ProductNoteBO NewProductNoteBO(bool isChild)
{
return new ProductNoteBO(isChild);
}


public static ProductNoteBO GetProductNoteBO(ProductNote item)
{
return new ProductNoteBO(item);
}


public static ProductNoteBO GetProductNoteBO(int productNoteId)
{
return DataPortal.Fetch(new Criteria(productNoteId));
}


public static void DeleteProductNoteBO(int productNoteId)
{
DataPortal.Delete(new Criteria(productNoteId));
}


private ProductNoteBO()
{ }


private ProductNoteBO(bool isChild)
{
if(isChild) MarkAsChild();
}


private ProductNoteBO(ProductNote item)
{
Fetch(item);
MarkAsChild();
}


Here we have introduced a isChild parameter which gives us the possibility to use this business object either as child object or as root object.

Code used to fetch data

private void Fetch(ProductNote item)
{
_productNoteId = item.ProductNoteID;
_version = item.Version;
_productNoteText = item.ProductNoteText;
MarkOld();
}


Code used to add and update data

internal void Insert(ISession session, Product parent)
{
InsertOrUpdate(parent, session);
}


internal void Update(ISession session, Product parent)
{
InsertOrUpdate(parent, session);
}


private void InsertOrUpdate(Product parent, ISession session)
{
if (base.IsDirty)
{
ProductNote item = new ProductNote();
item.ProductNoteID = _productNoteId;
item.Version = _version;
item.ProductNoteText = _productNoteText;
item.Product = parent; // set parent
session.SaveOrUpdate(item);


if (IsNew)
{
_productNoteId = item.ProductNoteID; // load generated ID!
_version = item.Version;
}


MarkOld();
}
}

196 Kommentare: