logo
Bamboo Prevalence Redux: BAMBlog Web app
(and Bamboo Prevalence on Compact Framework)
By Peter A. Bromberg, Ph.D.
Printer - Friendly Version
Peter Bromberg

Well, I've been fooling (fooling precedes serious study, which can precede serious use) with Bamboo Prevalence some more and so far I'm pretty impressed. If you aren't familiar with this piece of work, I'd suggest you visit my first article here, read up and look over the links, and then come back here.



As a side note, Stephan Meyn has been working on his "XSD Migration Kit" for BP, "Bamboo Builder", and his latest iteration now supports foreign keys. If you are interested in a tool that will take DataSets that you can drop onto a form, and use CodeDom to generate complete Bamboo Prevalence - compliant classes including a Loader that will get all your data out of your favorite database and save it as a snapshot from the BP engine, visit him at http://www.users.bigpond.net.au/meyn/Bamboo/.

N.B. 10/31/2003: Stephan upped a new article about his stuff on the codeproject.com site here.

I've been encouraging Stephan to put it out open source in some way, as its a concept that I believe has some significant uses especially in migrating apps. This is really the key to creating "standalone" distributable apps that include their own data store that is completely independent of any need to have a database on the user's machine. You just distribute the populated Bamboo Prevalence snapshot file, and your users are "good to go"! Now if somebody will just port the BinaryFormatter (all 66 classes enums and structs!) to the Compact Framework, you are ready to do Pocket PC apps with BP too. That would be nice. [Author's note: see N.B. below about CompactFormatter]

What I did here was take another look at what types of applications Bamboo Prevalence can be used for, without getting myself all involved in a whole big project. Most people who look at BP and are reluctant to begin using it are either still too "hung up" on abandoning their SQL as a way to manipulate their business objects, or else they make the somewhat fallacious claim that saving a snapshot of your data's state to the hard drive is too "risky". I say "fallacious" -- because this is exactly what SQL Server and other RDBMS's do- they save your data to files on the hard drive. So, far I'm convinced that BP does an excellent job of what it's supposed to do persistence-wise; that is, to log all the actions that would need to be replayed to recreate state faithfully and quickly to the persistence medium as advertised. In addition, you can exert as much or as little (e.g. TransparentPrevalence attribute) control over this process as you desire.

Now I'm not anti-database at all, I've spent a lot of time learning to work with Oracle, SQL Server, and others. But I really like the idea of representing your data with OOP-oriented classes and logic. It's much more intuitive, and lends to a cleaner business logic design pattern than always having to rely on ADO.NET and some sort of data access layer.

The only other issue I've seen come up is that developers say if they have a database that's really big, they're not too happy with the idea of storing 100% of it in memory. However, I believe that getting a huge increase in speed as well as being able to work with types (objects) that represent your business logic ( rather than SQL - oriented database and ADO.NET stuff) are benefits that should encourage developers to come up with strategies for doing partial loads from their regular RDBMS and so on, into the persistence engine. Since the project is open source and well - documented, enterprising coders should not find it too difficult to cobble on their own custom "snapshot takers" and so on. I'd like to see some example work in this area, so if you're a Bamboo Prevalence afficionado, please share it with us.

Finally, the Bamboo Prevalence classes are organized into nicely planned pieces. You can implement the IRecord interface, and this will give you an entree into the Collections, Indexing, FullText and XPathObjectNavigator classes that provide a rich set of methods for querying, searching, sorting and comparing your objects. It's a paradigm shift to be sure, but the benefits to independent thinkers who like this concept could be significant. Now let's take a look at my basic objects for implementing a web - based Blogging Engine (I decided to call it BAMBlog, for lack of anything more imaginative -- please forgive...).

First, your basic "Blog" item, a-la Bamboo Prevalence. Note that the only unusual things that may be seen here are that the class is marked with SerializableAttribute (required for the persistence engine) and that I've implemented IComparer, along with a little ReverserClass. This allows me to sort my entries by date descending so that the most recent entries appear first in the blog display.

using System;
using System.Diagnostics;
using System.Collections;
using Bamboo.Prevalence.Indexing;

namespace BlogLibrary
{
 public class  ReverserClass : IComparer  
 {
  int IComparer.Compare( Object x, Object y )  
  {
   return( (new CaseInsensitiveComparer()).Compare( y, x ) );
  }
 }
 [Serializable ]
 public class Blog : IComparable
 {
  private Guid mID;
  private string mUser;
  private string mPassword;
  private DateTime mBlog_Date;
  private string mBlog_Title;
  private string mBlog_Summary;
  private string mBlog_Text;
  private bool mEncrypted;
  private DateTime mLast_Updated;
  
  public string  ID{get{return mID.ToString();}}
  public string User{get{return mUser;}}
  public string Password{get{return mPassword;}}
  public DateTime Blog_Date{get{return mBlog_Date;}}
  public string Blog_Title {get{return mBlog_Title;}}
  public string Blog_Summary{get{return mBlog_Summary;}}
  public string Blog_Text
  {
   get{return mBlog_Text;}
   set{mBlog_Text=value;}
  }
  public bool Encrypted{get{return mEncrypted;}}
  public DateTime Last_Updated{get{return mLast_Updated;}}   
public int CompareTo(object obj) { if(obj is Blog) { Blog temp = (Blog) obj; return this.mBlog_Date.CompareTo(temp.mBlog_Date); } throw new ArgumentException("object is not a Blog"); } internal Blog(System.Guid ID,string User,string Password, DateTime Blog_Date, string Blog_Title, string Blog_Summary, string Blog_Text, bool Encrypted,DateTime Last_Updated) { mID=ID; mUser = User; mPassword=Password; mBlog_Date =Blog_Date; mBlog_Title=Blog_Title; mBlog_Summary =Blog_Summary; mBlog_Text=Blog_Text; mEncrypted=Encrypted; mLast_Updated=Last_Updated; } } }

OK, now we move to the BlogManager class, which handles all the details of a collection of our little "BAMBlogs":

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

namespace BlogLibrary
{
 [Serializable,TransparentPrevalence]
 public sealed class BlogManager : MarshalByRefObject,IRecord
 {
  static private readonly PrevalenceEngine mEngine;
  static public readonly BlogManager Instance = new BlogManager();
  static BlogManager()
  {
   string path=HttpContext.Current.Server.MapPath(".");
   mEngine = PrevalenceActivator.CreateTransparentEngine(typeof(BlogManager), path);
   Instance = mEngine.PrevalentSystem as BlogManager;
   SnapshotTaker taker = new SnapshotTaker(mEngine,TimeSpan.FromHours(1),
   Bamboo.Prevalence.Util.CleanUpAllFilesPolicy.Default);
    
  }
    
  private ArrayList mBlogs = new ArrayList();

  // IRecord Interface implementation (not used yet)--
  public object this[string strID]
   {
       get
     {
       int idx=mBlogs.BinarySearch(strID);
       if(idx <0)
        throw new ArgumentException("Item not found");
      return (Blog)mBlogs[idx];  
     }
   }
    

 public Blog CreateBlog(System.Guid ID,
 string User,string Password, DateTime Blog_Date, 
string Blog_Title, string Blog_Summary, string Blog_Text,
bool Encrypted,DateTime Last_Updated) { Blog blog = new Blog(ID, User, Password, Blog_Date, Blog_Title,Blog_Summary,
Blog_Text,Encrypted,Last_Updated); mBlogs.Add(blog); return blog; } [Query] public Blog[] GetAllBlogs( ) { // sort by most recent Date desc via IComparer IComparer myComparer = new ReverserClass(); mBlogs.Sort( myComparer ); return (Blog[])mBlogs.ToArray(typeof(Blog)); } [Query] public Blog[] GetAllBlogs(string strKey) { IComparer myComparer = new ReverserClass(); mBlogs.Sort( myComparer ); return (Blog[])mBlogs.ToArray(typeof(Blog)); } [Query] public Blog[] GetBlogsBySearchString(string srchString) { if(srchString=="") // no search term, send back everything { IComparer myComparer1 = new ReverserClass(); mBlogs.Sort( myComparer1 ); return (Blog[])mBlogs.ToArray(typeof(Blog)); } ArrayList returnedBlogs = new ArrayList(); for(int i=0;i<mBlogs.Count;i++) { Blog testBlog = (Blog)mBlogs[i]; if(testBlog.Blog_Summary.ToUpper().IndexOf(srchString.ToUpper())!=-1 ||
testBlog.Blog_Text.ToUpper().IndexOf(srchString.ToUpper())!=-1) returnedBlogs.Add(testBlog); } IComparer myComparer = new ReverserClass(); returnedBlogs.Sort( myComparer ); return (Blog[])returnedBlogs.ToArray(typeof(Blog)); } [Query] public Blog[] GetBlogsByDateRange(DateTime startDate, DateTime endDate) { ArrayList returnedBlogs = new ArrayList(); mBlogs.Sort(); for(int i=0;i<mBlogs.Count;i++) { Blog testBlog = (Blog)mBlogs[i]; if(testBlog.Blog_Date>=startDate && testBlog.Blog_Date<=endDate && testBlog.Blog_Text!="") returnedBlogs.Add(testBlog); } IComparer myComparer = new ReverserClass(); returnedBlogs.Sort( myComparer ); Debug.WriteLine("Count: " + returnedBlogs.Count.ToString()); return (Blog[])returnedBlogs.ToArray(typeof(Blog)); } public bool CreateNewUser(string userName, string password) { if (!AuthenticateUser(userName,password)) { Guid gooid =Guid.NewGuid(); CreateBlog(gooid,userName,password,DateTime.Now,userName + "'s Blog"
,""," ", false,DateTime.Now); return true; } else { return false; } } [Query] public Blog[] GetBlogByID(string strID) { ArrayList returnedBlogs = new ArrayList(); for(int i=0;i<mBlogs.Count;i++) { Blog testBlog = (Blog)mBlogs[i]; if(testBlog.ID.ToUpper()==strID.ToUpper() ) returnedBlogs.Add(testBlog); } return (Blog[])returnedBlogs.ToArray(typeof(Blog)); } [Query] public Blog[] GetBlogByUser(string userName) { ArrayList returnedBlogs = new ArrayList(); for(int i=0;i<mBlogs.Count;i++) { Blog testBlog = (Blog)mBlogs[i]; if(testBlog.User.ToUpper()==userName.ToUpper() ) returnedBlogs.Add(testBlog); } IComparer myComparer = new ReverserClass(); returnedBlogs.Sort( myComparer ); return (Blog[])returnedBlogs.ToArray(typeof(Blog)); } [Query] public bool AuthenticateUser(string userName,string password) { for(int i=0;i<mBlogs.Count;i++) { Blog testBlog = (Blog)mBlogs[i]; if(testBlog.User==userName && testBlog.Password ==password ) return true; } return false; } public Blog UpdateBlog( string strID,string User, string Password,DateTime Blog_Date,
string Blog_Title,string Blog_Summary, string Blog_Text,
bool Encrypted,DateTime Last_Updated) { Blog blog=null; ArrayList returnedBlogs = new ArrayList(); for(int i=0;i<mBlogs.Count;i++) { Blog testBlog = (Blog)mBlogs[i]; if(testBlog.ID==strID ) { mBlogs.RemoveAt(i); Guid gooid = Guid.NewGuid(); blog = new Blog(gooid,User,Password,Blog_Date, Blog_Title,Blog_Summary,Blog_Text,
Encrypted,Last_Updated); mBlogs.Add(blog); } } return blog; } public void DeleteBlog(string strID) { for(int i=0;i<mBlogs.Count;i++) { Blog testBlog = (Blog)mBlogs[i]; if(testBlog.ID==strID) { mBlogs.RemoveAt(i); } } } public void TakeSnapshot() { mEngine.TakeSnapshot(); } } }

In particular, take note of this code:

SnapshotTaker taker = new SnapshotTaker(mEngine,TimeSpan.FromHours(1), Bamboo.Prevalence.Util.CleanUpAllFilesPolicy.Default);

--which tells the engine to take a full snapshot and automatically clean up all old unneccessary .snapshot and .commandLog files once per hour, which it faithfully does. That's really overkill for a web app, because the engine can use the commandlogs from each session to completely restore the state. A more realistic one would be say, once every 12 hours. This just keeps the size down, and prevents having a large collection of files. You can see the other methods are all basically to get blogs by date, search string, and so on. I have a field "Encrypted" that I intended to use for private blogs but I decided that was overkill.

You can see that in order to keep the complexity down, I've put the username and password in as fields for each Blog item. This serves a dual pupose; I want to display the user with each blog item in the UI, and I also wanted to provide an easy method for authentication without having to add another class and write a bunch of pseudo "JOIN" code. So my "AuthenticateUser" method becomes quite simple, yet very functional:

[Query]
public bool AuthenticateUser(string userName,string password)
{
for(int i=0;i<mBlogs.Count;i++)
{
Blog testBlog = (Blog)mBlogs[i];
if(testBlog.User==userName && testBlog.Password ==password )
return true; }
return false;
}

This allows us to create a multi-user blog where visitors can see all the blog items, but users can log in and be able to add and edit only their own items. I haven't spent a lot of time on this, so it's still a bit rough around the edges, but it's been a lot of fun and very instructional in learning how to use the Bamboo Prevalence Engine.

The rest of the code mostly deals with the UI - Webforms and codebehind for display (2 kinds - a DataGrid view and a more classic "Blog View"), search, date range, LogIn and New User, Edit blog, Add Blog, and so on. You can try it out here: I'd be interested in reader feedback on this one - comments, new ideas and whatever. Feel free to post ideas and comments to our forums here.

N.B. Angelo Scotto has put together what he calls the CompactFormatter . The CompactFormatter is a very lean (24K) set of classes that mimic the BinaryFormatter and is 100% Compact Framework compatible. To use CompactFormatter with Bamboo Prevalence, all you need to do is replace all references to BinaryFormatter with CompactFormatter, and disambiguate the [Serializable] attributes by setting a "using yo=Serialization.Formatters" and then [yo.Serializable] wherever there is a [Serializable] decoration. Finally, you'll need to supply a parameterless constructor for any classes that do not have one, as the CompactFormatter requires this to load and interrogate the class via Reflection. I've wired this up as described, and it works great. Sweet!

There is another problem, however, in porting Bamboo Prevalence to the Compact Framework, and that is the fact that BP in it's current iteration requires the use of the Remoting namespaces, which aren't present in the Compact Framework. Until some enterprising soul comes up with an alternative way to handle the creation of the BP persistence engine for CF, that pretty much does in any chance of successfully porting Bamboo Prevalence to Compact Framework.

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. Pete's samples at GotDotNet.com have been downloaded over 41,000 times. You can read Peter's UnBlog Here.  --><--NOTE: Post QUESTIONS on FORUMS!
Do you have a question or comment about this article? Have a programming problem you need to solve? Post it at eggheadcafe.com forums and receive immediate email notification of responses.