Saturday, March 24, 2007

How the automatic persistence magic is woven - Part 1

One of the benefits of O/R Mapping is supposed to be transparency of loading and storing data. You should not need to care exactly when and how data is stored. Just focus on creating and manipulating and selcting your objects. Except for some general interaction with the O/R Mapping infrastructure, you just do, whatever you would do with your objects if they were not persistent:

   19 Person p;

   20 p = new Person("Eric", "Doe", new DateTime(1972, 2, 17),

   21                new Address("123 Wisconsin Ave NW", "Washington, DC", "22099", "USA"));

   22 os.Add(p);


   24 p.firstname = "Denis";

   25 = "San Francisco, CA";

Line 19 creates two objects, line 24 changes an object. Just line 22 is a tribute to the O/R Mapper: you register the root object of a persistent object tree with the O/R Mapper. But this is all straightforward OOP; you deal with your objects/class and not some generic stuff like an ADO.NET DataSet.

What you don´t see, though, is, how your objects are persisted. You don´t call a Save() method on individual objects - at least not with OpenAccess; other O/R Mappers might require you to do so -, especially you don´t need to care for making each and every individual object persistent. Dependent objects like the Address in line 21 are automatically persisted with their parent objects - at least that´s how OpenAccess works, it´s called persistence by reachability.

However, at some point you have to tell the O/R Mapper to persist the changes to the overall set of persistent objects you´ve loaded and modified, deleted, or created. With OpenAccess this is done by closing a transaction which means: before you change anything on persistent objects you need to open such a transaction either through the OpenAccess API:

   15 using (OpenAccess.IObjectScope os = OpenAccess.Database.Get("DatabaseConnection1").GetObjectScope())

   16 {

   17     os.Transaction.Begin();

   18     ...

   26     os.Transaction.Commit();

   27 }

... or use the .NET System.Transactions namespace:

   45 OpenAccess.Database db = OpenAccess.Database.Get("DatabaseConnection1");

   46 db.Properties.TransactionProvider = OpenAccess.TransactionProvider.TransactionScope;

   47 using (OpenAccess.IObjectScope os = db.GetObjectScope())

   48 {

   49     using (System.Transactions.TransactionScope tx = new System.Transactions.TransactionScope())

   50     {

   51         ...

   57     }

   58 }

Warning: Using TransactionScope like above and what´s called implicit transactions by OpenAccess only works with SQL Server and Oracle 9i (or higher), but not with SQL Express! Also you should not mix OpenAccess transaction usage with System.Transactions, since you would need to switch between the two modes by setting the database TransactionProvider property (line 46 above) all the time. The TransactionProvider needs to be set before an IObjectScope for the database is opened.

So the overall signal to the O/R Mapper to store any changes made to persistent objects is a transaction´s commit command. Only during this commit SQL INSERT, UPDATE, DELETE statements will get issued to transfer the current state of the objects to the database. That also means, a real database transaction will only be necessary now.

Click on image to enlarge 

Now, the magic of O/R Mapping is happening during lazy loading and during commit. How does the O/R Mapper know, when to load more objects, e.g. the Address for a Person? And how does the O/R Mapper determine which objects to store and which field values have changed?

This magic obviously has to be woven by code behind the scenes. The class definitions are void of any infrastructure code to that purpose. Remember: You just need to adorn a class with the OpenAccess.Persistent attribute to make it a persistent class.

Here´s the solution to the magic trick. Let´s look into the magician´s top hat:

  225 using (IObjectScope os = Database.Get("DatabaseConnection1").GetObjectScope())

  226 {

  227    os.Transaction.Begin();

  228    Person p = new Person("Eric", "Doe", new DateTime(0x7b4, 2, 0x11), 
                               new Address("123 Wisconsin Ave NW", "Washington, DC", "22099", "USA"));

  229    os.Add(p);

  230    Person.OpenAccessEnhancedSetfirstname(p, "Denis");

  231    Address.OpenAccessEnhancedSetcity(Person.OpenAccessEnhancedGethomeAddress(p), "San Francisco, CA");

  232    os.Transaction.Commit();

  233 }

Thanks to Lutz Roeder´s Reflector this is the code I was able to extract from the assembly the above code (lines 19 to 27) was compiled into. But compare lines 230 and 231 in this listing with lines 24 and 25 in the first listing above. There is an astonishing difference: simple field assignments got replaced by calls to static methods. But I did not declare any static methods on the Person and Address classes. Where do they come from? For an answer see my next article...

1 comment:

FleaCircusDirector said...

Minor typo:

Line 19 creates two objects
should be
Line 20 creates two objects