65.9K
CodeProject is changing. Read more.
Home

Transactions on object models

starIconstarIconstarIconstarIconemptyStarIcon

4.00/5 (5 votes)

Mar 8, 2009

GPL3

2 min read

viewsIcon

33452

downloadIcon

229

Rollback or commit changes that you did on .NET objects.

Introduction

Wouldn't it be nice if we could (like in a database) take an object-oriented domain model, start a transaction, do wild things with the data and relationships, and then eventually just roll back these changes? My opinion: Yes, it would! Especially if we tend to use as "less" as possible of the functionalities of a relational database and as much as possible the benefits of object orientation. In those system designs, the possibility of managing object model transactions is necessary. For the TechNewLogic "Stasy" framework - a high level implementation of the Unit-Of-Work design pattern (http://martinfowler.com/eaaCatalog/unitOfWork.html), I wrote a little demo extension that makes transactions possible.

Summary

  • Automatic tracking of property changes via a specialized lightweight .NET proxy.
  • The proxy inherits the entity types at runtime.
  • These types must be registered at the proxy generator (see example below).
  • Limitation: The entity classes of the object model have to be created via a factory method of the proxy generator.
  • Limitation: The entity classes must have a default (parameter-less) constructor.
  • Limitation: All properties of the entity classes must be declared as "virtual" so that the runtime inheritance mechanism of the proxy can work.
  • Limitation: If lists or collections are used in the entity classes, an implementation of ITransactionList has to be used (there is a standard implementation in the framework).

Example

public class EntityClass1
{
    public EntityClass1()
    {
        EntityClass2List = new TransactionList<EntityClass2>();
    } 
    
    public virtual int MyInt { get; set; } 
    public virtual TransactionList<EntityClass2> EntityClass2List { get; set; }
}

public class EntityClass2
{
    public virtual string MyString { get; set; }
}

Here, we have quite a simple object model. It consists of two classes: one has an aggregation to the other and some primitive properties.

The following code snippet demonstrates how to use the transaction framework. Basically, it works like this:

  • Create some data (we use our small object model, of course).
  • Begin a transaction.
  • Now, we do some stuff with our model, change values, insert some new entities, etc.
  • Rollback
  • Result: The object model looks as if nothing happened!
static void Main(string[] args)
{
    // Setup
    var transactionMonitor = new TransactionMonitor();
    var generator = new ProxyGenerator(
        new[]
        {
            typeof(EntityClass1),
            typeof(EntityClass2)
        },
        transactionMonitor);

    // Create the model data
    var entity1 = generator.CreateProxy<EntityClass1>();
    entity1.MyInt = 100;
    AssertAreEqual(100, entity1.MyInt);

    entity1.EntityClass2List.Add(generator.CreateProxy<EntityClass2>());
    var entity2 = entity1.EntityClass2List.Single();
    entity2.MyString = "Hallo";
    AssertAreEqual("Hallo", entity2.MyString);

    
    ///
    /// everything done from here will be tracked and eventually rolled back
    ///
    transactionMonitor.Begin(); 

    // change a value property of our entity1
    entity1.MyInt = 9999;
    AssertAreEqual(9999, entity1.MyInt); 

    // change a value property of our entity2
    entity2.MyString = "Welt";
    AssertAreEqual("Welt", entity2.MyString); 
    
    // add a new entity to the list of entity 1 and check if we had success
    var newEntity = generator.CreateProxy<EntityClass2>();
    entity1.EntityClass2List.Add(newEntity);
    AssertAreEqual(2, entity1.EntityClass2List.Count); 
    
    // remove the 'original' entity2; the only remaining
    // entity should be the new entity
    entity1.EntityClass2List.Remove(entity2);
    var checkEntity = entity1.EntityClass2List.Single();
    AssertAreEqual(newEntity, checkEntity); 
    
    ///
    /// Rollback all our changed
    /// 
    transactionMonitor.Rollback(); 
    
    // the value property of entity1
    AssertAreEqual(100, entity1.MyInt); 

    // there should be one entity in the entity list of entity1
    var checkEntity2 = entity1.EntityClass2List.Single();
    AssertAreEqual(entity2, checkEntity2); 

    // the value property of entity2
    AssertAreEqual("Hallo", entity2.MyString); 

    Console.ReadLine();
} 

static void AssertAreEqual(object o1, object o2)
{
    if (o1 == null && o2 == null)
        return;
    else if (o1 == null && o2 != null)
        throw new ApplicationException();
    else if (o1 != null && o2 == null)
        throw new     ApplicationException();
    else if (!o1.Equals(o2))
        throw new ApplicationException();
}