Bamboo Prevalence: .NET Object Persistence Engine
(Look Ma! - No DataBase!)
By Peter A. Bromberg, Ph.D.
Printer - Friendly Version
Peter Bromberg

You know how it is when your life is filled up with "stuff" and you can never seem to find time to do all the things you want to do? Sometimes we come across a new concept, we look at it, and we decide to take a pass for the time being. That's kind of the way object -relational and object persistence engines and I have gotten along over the past year or so. However, after I got a look at Bamboo Prevalence, orchestrated by Rodrigo de Oliveira and domiciled at sourceforge.net, I decided it was worth the time to study. (Anything on SourceForge with "5 -Production/Stable" is likely to be a more rewarding experience, IMHO).



What is Bamboo Prevalence?

First, Bamboo Prevalence (herein, "BP") is not a database. It's an object persistence server / mechanism. You don't work with DataSets, DataReaders and SQL - you work with objects, such as classes, that you would normally use in OOP programming practices to represent your business logic anyway. Bamboo provides an "engine" that offers a way to transparently call your methods and will automatically create the required command and query mechanism "behind the scenes" to persist your changes. Since the entire persistence mechanism -- including your collection of business objects -- is in memory, it's lightning fast.

Anything that can be marked as Serializable can be persisted and handled via BP. That means if you have a "record" that you would normally get from a row in your RDBMS, you can now simply create a class to represent it, and another class to hold a collection of them, and have BP handle all the serialization and persistence transparently for you. With only the most minor of changes, you can still do all the cool DataBinding things you're used to doing in .NET with DataGrids and other cool widgets (ArrayLists databind very nicely, thank you).

How Persistence is handled

BP handles persistence through two features: the CommandLog (basically the equivalent of the SQL Server Transaction Log), which automatically records everything you did and keeps it in a binary file on disk, and the Snapshot, which can be used to "take a picture" of your business objects through the BinaryFormatter mechanism and keep it in a file on disk. It does this through the Remoting infrastruture, aquiring and releasing reader/writer locks to prevent collisions. The BP infrastructure increments the ending file numbers of these files and knows how to automatically rehydrate your "stuff" when the application is restarted. It's like, "Look, Ma! - No Database!". I really mean it -- you can have a million objects in memory, and BP will faithfully handle the serialization, persistence, and restoration of same. Now a lot of skeptical people who are really hung up on their Oracles and their SQL Servers and not gonna like this. But, hey; just suspend judgement for the time being and try some stuff, and then you decide. Nobody's holding a gun to your head.

I got more interested in BP because i saw an excellent "Starter" article by Arjan Einbu at CodeProject.com. Einbu took the time to wire up the basics for a Windows Forms "Contact Manager" program. I'd recommend this as a good start to get involved in how to use BP. There are also some samples in the BP distribution. I'd recommend that when you start a Bamboo Prevalence solution, you bring in the actual BP source projects rather than just setting a reference to the needed assemblies. In this manner you can set some breakpoints and trace through the BP code to actually see what's really going on "under the hood". You'll get comfortable with the whole scenario faster this way.

The ContactManagerWeb ASP.NET application

What I've done here is to set up a faux "Contact Manager" web application using BP. I've only used two of the Bamboo assemblies - the base Bamboo.Prevalence layer, and the Bamboo.Prevalence.Util layer, which is used to perform automatic "cleanup" of un-needed persistence files once a hour. There are also classes for XPath (including a nice implementation of an XPath Object Navigator) , Indexing, and Collections for working with your objects.

My two business logic operating classes are similar to the ones created by Einbu in his article, only they are much more "fleshed out" with methods for Updates, searches, and deletes, as well as a hook into System.Web to handle the persistence files in the web folder.

First we have the Contact Class, which represents your basic Contact record:

using System;
using System.Diagnostics;

namespace ContactManagerLibrary
{
 [Serializable ]
 public class Contact
 {
  private Guid mID;
  private string mName;
  private string mPostalAddress;
  private string mEmailAddress;
  private string mPhoneNumber;
  
  public string  ID{get{return mID.ToString();}}
  public string Name{get{return mName;}}
  public string PostalAddress{get{return mPostalAddress;}}
  public string EmailAddress{get{return mEmailAddress;}}
  public string PhoneNumber{get{return mPhoneNumber;}}

  internal Contact(System.Guid ID,string name, string postalAddress, 
string emailAddress, string phoneNumber) { mID=ID; mName = name; mPostalAddress = postalAddress; mEmailAddress = emailAddress; mPhoneNumber = phoneNumber; } } }

You can see that I'm using a Guid for the unique ID (I often do this in databases too, as many other developers have started doing). Nothing earth-shattering so far, eh?

Now here's the ContactManager Class, which fleshes out the methods and hold my collection of Contacts:

using System;
using System.Collections;
using System.IO;
using Bamboo.Prevalence;
using Bamboo.Prevalence.Attributes;
using Bamboo.Prevalence.Util;
using System.Web;

namespace ContactManagerLibrary
{
 [Serializable,TransparentPrevalence]
 public sealed class ContactManager : MarshalByRefObject
 {
  static private readonly PrevalenceEngine mEngine;
  static public readonly ContactManager Instance = new ContactManager();
  static ContactManager()
  {
   string path=HttpContext.Current.Server.MapPath(".");
   mEngine = PrevalenceActivator.CreateTransparentEngine(typeof(ContactManager), path);
   Instance = mEngine.PrevalentSystem as ContactManager;
   SnapshotTaker taker = new SnapshotTaker(mEngine,TimeSpan.FromHours(1), 
                        Bamboo.Prevalence.Util.CleanUpAllFilesPolicy.Default);
  }
  
private ArrayList mContacts = new ArrayList();
  public Contact CreateContact(Guid ID,string name, string postalAddress, 
string
emailAddress, string phoneNumber) { Contact contact = new Contact(ID, name, postalAddress, emailAddress, phoneNumber); mContacts.Add(contact); return contact; } public Contact[] GetAllContacts() { return (Contact[])mContacts.ToArray(typeof(Contact)); } public Contact[] GetContactsByName(string srchName) { ArrayList returnedContacts = new ArrayList(); for(int i=0;i<mContacts.Count;i++) { Contact testContact = (Contact)mContacts[i]; if(testContact.Name.ToUpper().IndexOf(srchName.ToUpper())!=-1) returnedContacts.Add(testContact); } return (Contact[])returnedContacts.ToArray(typeof(Contact)); } public Contact[] GetContactByID(string strID) { ArrayList returnedContacts = new ArrayList(); for(int i=0;i<mContacts.Count;i++) { Contact testContact = (Contact)mContacts[i]; if(testContact.ID.ToUpper()==strID.ToUpper() ) returnedContacts.Add(testContact); } return (Contact[])returnedContacts.ToArray(typeof(Contact)); } public Contact UpdateContact(string strID,string strName, string strAddress,
string strEmail,string strPhone) { Contact contact=null; ArrayList returnedContacts = new ArrayList(); for(int i=0;i<mContacts.Count;i++) { Contact testContact = (Contact)mContacts[i]; if(testContact.ID==strID ) { mContacts.RemoveAt(i); Guid gooid = Guid.NewGuid(); contact = new Contact(gooid, strName, strAddress, strEmail, strPhone); mContacts.Add(contact); } } return contact; } public void DeleteContact(string strID) { for(int i=0;i<mContacts.Count;i++) { Contact testContact = (Contact)mContacts[i]; if(testContact.ID==strID) { mContacts.RemoveAt(i); } } } public void TakeSnapshot() { mEngine.TakeSnapshot(); } } }

Note how the class itself is decorated with both the required Serializable attribute and the TransparentPrevalence attribute, with tells the engine to automatically intercept all of our method calls and ensure they are persisted, without our having to worry about any details at all. If we did not use the TransparentPrevalence attribute, we could decorate the method with [Query] or [Command] attributes to indicate they are read only or need to be persisted, respectively. Also note how we create the static engine instance:

static ContactManager() {
string path=HttpContext.Current.Server.MapPath(".");
mEngine = PrevalenceActivator.CreateTransparentEngine(typeof(ContactManager), path);
Instance = mEngine.PrevalentSystem as ContactManager;
SnapshotTaker taker = new SnapshotTaker(mEngine,TimeSpan.FromHours(1), Bamboo.Prevalence.Util.CleanUpAllFilesPolicy.Default);
}

You can see near the end that I'm passing the mEngine instance to the SnapshotTaker, telling it to take a snapshot every hour, using the CleanUpAllFiles policy Interface which basically cleans up all command log and snaphot files that are no longer necessary. Also, in my Global.asax I call the Snapshot method in Application_End.

Now the cool stuff! In my Default.aspx page, in Page_Load, this is all I need:

DataGrid1.DataSource = ContactManager.Instance.GetAllContacts();
DataGrid1.DataBind();

And, for example, in the EditContact.aspx page:

txtID.Value=Convert.ToString(Request.QueryString["ID"]);
string name = Request.Form["txtName"].ToString();
string postalAddress = Request.Form["txtPostalAddress"].ToString();
string emailAddress = Request.Form["txtEmailAddress"].ToString();
string phoneNumber = Request.Form["txtPhoneNumber"].ToString();
ContactManager.Instance.UpdateContact(txtID.Value , name,postalAddress,emailAddress,phoneNumber);
Server.Transfer("Default.aspx");

And that's pretty much it! The download solution zip file should be self-explanatory, simply make the ContactManagerWeb folder an IIS VRoot and mark it as an IIS Application and you are good to go. If you'd like to play with it "live" as a proof of concept, I've deployed a real live "Look Ma, no Database" copy of it here. (Please understand we are not responsible for the incredible JUNK that I am sure people will be putting in there).

N.B.-- Stephan Meyn has been working on an "XSD Migration Kit" for BP that he calls, appropriately, "Bamboo Builder". This is one of the best implementations of CodeDom work I've seen in this area, and represents the "glue" to migrate strongly - typed Datasets to Bamboo Prevalence. Kudos to Stephan for his fine work, even though it's still in Alpha - visit him at http://www.users.bigpond.net.au/meyn/Bamboo/. Stephan indicated to me that he has renewed enthusiasm for this, so look for more good things to happen.

Download the Source Code that accompanies this article

 


Peter Bromberg is a C# MVP, MCP, and .NET consultant who has worked in the banking and financial industry for 20 years. He has architected and developed web - based corporate distributed application solutions since 1995, and focuses exclusively on the .NET Platform.