Developing on Staxmanade

Db4o with System.Transactions.TransactionScope

After after reading Rob Conery's post http://blog.wekeroad.com/blog/crazy-talk-reducing-orm-friction/. I thought I'd give it a whirl and didn't get very far before I ran into my first roadblock. DB4O version 7.4.68 doesn't support any .NET System.Transaction. (For what I could tell...)

Below I'll explain how I pieced this together and made it work (At least an alpha type version to work...)

Assume I've followed almost everything Rob has in his blog for setup (IRepository, DB4O container class, ObjectRepository<T>, etc...)

My first unit test was the following

[Test]
public void CanRollbackSavedObjectInRepository()
{
  using (TransactionScope scope = new TransactionScope())
  {
    _repository.Save(_dummyObject);
    //NO: scope.Complete(); Implied rollback
  }

  if (_repository.GetAll().Count() > 0)
    Assert.Fail("TransactionScope Failed - count[" + _repository.GetAll().Count().ToString() + "]");
}

And the above test fails...

Next was some research to figure out why this was failing... I didn't find much out there, except for some pending TODO's on the DB4O project.

http://tracker.db4o.com/browse/COR-1376
http://tracker.db4o.com/browse/COR-1143

 

So then it was off to figure out how frameworks implemented there resource managers to hook into the System.Transaction goo...

(Long story short, and several trial and error failures) The best of the solutions I found was in this open source code base. (http://code.google.com/p/uniframework/)

In that project there was a class that managed the enlistment of a transaction, and some examples of how to use it...

http://code.google.com/p/uniframework/source/browse/trunk/sources/Uniframework/Db4o/Db4oEnlist.cs is the enlistment class.

 

I placed this Db4oEnlist class as a private nested class inside the ObjectRepository<T> since I can't see why it would be used anywhere else...

And below is how I use them in the ObjectRepository<T>.

public void Delete(T item)
{
Db4oEnlist enlist = new Db4oEnlist(Container, item);
bool inTransaction = Enlist(enlist);
Container.Delete(item);
if (!inTransaction)
Container.Commit();
}


public void Save(T item)
{
Db4oEnlist enlist = new Db4oEnlist(Container, item);
bool inTransaction = Enlist(enlist);
Container.Store(item);
if (!inTransaction)
Container.Commit();
}


 



We first create an instance of the Db4OEnlist class with the current container. This class implements the IEnlistmentNotification interface and knows how to commit/rollback/etc on the object database. We then use the private helper method Enlist() giving it the Db4OEnlist instance. This helper method enlists the sequence in any existing transactions returning if it enlisted in a transaction or not.



private static bool Enlist(Db4oEnlist enlist)
{
System.Transactions.Transaction currentTx = System.Transactions.Transaction.Current;
if (currentTx != null)
{
currentTx.EnlistVolatile(enlist, EnlistmentOptions.None);
return true;
}
return false;
}


If we aren't in a transaction we commit the action right away, however if we are in the transaction we let the .net System.Transaction framework take care of committing the transaction.



Once I found the example in the "uniframework" it came together rather quickly...



Below is the ObjectRepository<T> I'm going forward with on testing Rob's idea of developing an application using the object database first... (we'll see how it goes)



public class ObjectRepository<T> :
IRepository<T> where T : class
{
/// <summary>
/// Returns all T records in the repository
/// </summary>
public IQueryable<T> GetAll()
{
return (from T items in Container
select items).AsQueryable();
}

/// <summary>
/// Finds an item using a passed-in expression lambda
/// </summary>
public IQueryable<T> Find(System.Linq.Expressions.Expression<Func<T, bool>> expression)
{
return GetAll().Where(expression);
}

/// <summary>
/// Saves an item to the database
/// </summary>
/// <param name="item"></param>
public void Save(T item)
{
Db4oEnlist enlist = new Db4oEnlist(Container, item);
bool inTransaction = Enlist(enlist);
Container.Store(item);
if (!inTransaction)
Container.Commit();
}

/// <summary>
/// Deletes an item from the database
/// </summary>
/// <param name="item"></param>
public void Delete(T item)
{
Db4oEnlist enlist = new Db4oEnlist(Container, item);
bool inTransaction = Enlist(enlist);
Container.Delete(item);
if (!inTransaction)
Container.Commit();
}

private static bool Enlist(Db4oEnlist enlist)
{
System.Transactions.Transaction currentTx = System.Transactions.Transaction.Current;
if (currentTx != null)
{
currentTx.EnlistVolatile(enlist, EnlistmentOptions.None);
return true;
}
return false;
}

private static IObjectContainer Container
{
get
{
return ServiceLocator.Current.GetInstance<IObjectContainer>();
}
}

/// <summary>
/// Provides support for System.Transaction integration
/// </summary>
private class Db4oEnlist : IEnlistmentNotification
{
private IObjectContainer container;
private object oldItem;

/// <summary>
/// Initializes a new instance of the <see cref="db4oEnlist"/> class.
/// </summary>
/// <param name="database">The database.</param>
/// <param name="item">The item.</param>
public Db4oEnlist(IObjectContainer container, object item)
{
this.container = container;
oldItem = item;
}

#region IEnlistmentNotification

/// <summary>
/// Commits the specified enlistment.
/// </summary>
/// <param name="enlistment">The enlistment.</param>
public void Commit(Enlistment enlistment)
{
container.Commit();
oldItem = null;
}

/// <summary>
/// Ins the doubt.
/// </summary>
/// <param name="enlistment">The enlistment.</param>
public void InDoubt(Enlistment enlistment)
{
//throw new Exception("The method or operation is not implemented.");
}

/// <summary>
/// Prepares the specified preparing enlistment.
/// </summary>
/// <param name="preparingEnlistment">The preparing enlistment.</param>
public void Prepare(PreparingEnlistment preparingEnlistment)
{
preparingEnlistment.Prepared();
}

/// <summary>
/// Rollbacks the specified enlistment.
/// </summary>
/// <param name="enlistment">The enlistment.</param>
public void Rollback(Enlistment enlistment)
{
container.Rollback();
container.Ext().Refresh(oldItem, int.MaxValue);
}

#endregion
}
}

Comments

Magento theme
I found so many interesting stuff in your blog especially its discussion. So i must appreciate your efforts on posting these information.
German Viscuso
Hi!

You’ve been nominated as db4o dVP. Please see this page
I don’t have your contact info!

Best!

German
German Viscuso
Great stuff!
See:

http://developer.db4o.com/blogs/community/archive/2008/12/11/db4o-with-system-transactions-transactionscope.aspx

Best regards.

German Viscuso
db4objects community manager