| 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 |