Coming in 2010.2 is the new ExplicitUnitOfWork. This class derives from UnitOfWork and allows you to easily use explicit transactions in XPO. The advantage of this is that it works with objects as if they were already stored in the database, meaning it appears – to the developer – as if there is no difference between objects in memory and data in the database. The only downside of this is that the class must be the exclusive owner of the database connection.
Let’s demonstrate this with an example. First, we will declare a Person class:
using DevExpress.Xpo;
public class Person: XPLiteObject {
int oid;
string name;
int age;
[Key(true)]
public int Oid {
get { return oid; }
set { SetPropertyValue<int>("Oid", ref oid, value); }
}
public string Name {
get { return name; }
set { SetPropertyValue<string>("Name", ref name, value); }
}
public int Age {
get { return age; }
set { SetPropertyValue<int>("Age", ref age, value); }
}
public Person(Session session)
: base(session) {
}
}
And then make a few tests with the ExplicitUnitOfWork:
using System;
using DevExpress.Xpo;
using DevExpress.Xpo.DB;
using DevExpress.Data.Filtering;
CriteriaOperator criteria;
//Assume the database is empty
using(ExplicitUnitOfWork euow = new ExplicitUnitOfWork(dataLayer)) {
Person person = new Person(euow);
person.Name = "Thomas Brown";
person.Age = 34;
//Try to load from the database object with the same name and the age of
criteria = CriteriaOperator.Parse("Name = ? AND Age = ?", person.Name, person.Age);
Person personFromDB = euow.FindObject<Person>(criteria);
//Check that the database loaded the newly created object
System.Diagnostics.Debug.Assert(ReferenceEquals(person, personFromDB));
System.Diagnostics.Debug.Assert(person.Oid == personFromDB.Oid);
}
using(ExplicitUnitOfWork euow = new ExplicitUnitOfWork(dataLayer)) {
//Try again to load the object from the DB
Person personFromDB = euow.FindObject<Person>(criteria);
//At this time the object is not loaded, because the changes we do not save
System.Diagnostics.Debug.Assert(ReferenceEquals(personFromDB, null));
}
Note, all of the above applies to objects loaded via XPCollection and XPView.
So, how does this work? Well, the trick is that ExplicitUnitOfWork opens an explicit transaction when modifying or creating any object. When loading, it saves all of the changes to the database so that it contains the modified objects. When the CommitChanges method is called, the ExplicitUnitOfWork saves the last modification and closes the explicit transaction. After that, all the changes are visible to the entire database.
Let’s modify our previous example:
using DevExpress.Xpo;
using DevExpress.Xpo.DB;
using DevExpress.Data.Filtering;
CriteriaOperator criteria;
int personOid;
using(ExplicitUnitOfWork euow = new ExplicitUnitOfWork(dataLayer)) {
Person person = new Person(euow);
person.Name = "Thomas Brown";
person.Age = 34;
criteria = CriteriaOperator.Parse("Name = ? AND Age = ?", person.Name, person.Age);
Person personFromDB = euow.FindObject<Person>(criteria);
System.Diagnostics.Debug.Assert(person.Oid == personFromDB.Oid);
System.Diagnostics.Debug.Assert(ReferenceEquals(person, personFromDB));
//Remember the OID of the object
personOid = person.Oid;
//This time will commit changes
euow.CommitChanges();
}
using(ExplicitUnitOfWork euow = new ExplicitUnitOfWork(dataLayer)) {
//Try again to load the object from the DB
Person personFromDB = euow.FindObject<Person>(criteria);
//Object loaded...
System.Diagnostics.Debug.Assert(!ReferenceEquals(personFromDB, null));
//...and the object of one buyout, we retain
System.Diagnostics.Debug.Assert(personFromDB.Oid == personOid);
}
Now let’s think of a scenario, in which the ExplicitUnitOfWork may come in handy. Suppose there are fields, which have unique constraints in the database, and we need to make changes that can violate these constraints. In other words, we would need to remove the object and create a new one, but with the same identifier.
Let’s modify our Person class and make its Oid field not auto-incremented:
[Key]
public int Oid {
get { return oid; }
set { SetPropertyValue<int>("Oid", ref oid, value); }
}
And then do the following:
using(ExplicitUnitOfWork euow = new ExplicitUnitOfWork(dataLayer)) {
//Load the object with a key ‘5’
Person person = euow.GetObjectByKey<Person>(5);
//Delete it
euow.Delete(person);
//Save the changes within a transaction
euow.FlushChanges();
//Create a new object with a key ‘5’
Person personNew = new Person(euow);
personNew.Oid = 5;
personNew.Name = "Thomas Brown";
personNew.Age = 34;
//Save everything and close the transaction
euow.CommitChanges();
}
We have also created another example demonstrating the use of the ExplicitUnitOfWork class. You can download it and check it out, after 2010.2 ships. In this sample you will see:
“…a general solution, showing how to use the ExplicitUnitOfWork class to generate sequential numbers, that can then be used as user-friendly identifiers in documents, as invoice line numbers, etc. Here, as opposed to the solution shown in the A2213 KB Article, the ExplicitUnitOfWork class is used to ensure that the assignment of the sequential number will be a part of a database transaction that results in the successful saving of the document. Hence, the current solution will also work in a scenario with high-volume of concurrency transactions.”
That’s all for this blog post, ‘til next time, happy XPO’ing! 