Let's write an app with Db4o and ASP.NET MVC again! Check out this post first.

We've kept it simple & stupid: a model with two entities and a one-to-many relationship. It should set a base for most apps though:

A little background: in a data centre there are serverracks, with blade enclosures or blade chassis. In these enclosures, you can put servers. So, bladeservers go into one blade chassis. Here is the typical one to many relationship!

This is how I set up the solution in Visual Studio:

  • Core contains the business entities
  • Tasks is a console app to get things done quick and dirty
  • Test is for the Unit tests
  • Web is the ASP.NET MVC3 application (Empty) with Razor

So, just create a new class in Core and make it look like this:

namespace Core
{
public class BladeServer
{
public string Hostname { get; set;}
public string ServerRole { get; set; }
public string IPAddress { get; set; }
public string OS { get; set; }
public BladeChassis BladeChassis { get; set; }
}
//the bladeserver goes into the bladechassis
public class BladeChassis
{
public string ChassisID { get; set; }
public string Description { get; set; }
public int UnitsHigh { get; set; }
public int Bays { get; set; }
public string Power { get; set; }
public List<BladeServer> Servers { get; set; }
}
}

Now go to the Db4o website quickly to grab db4o for .NET 4.0, version 8.0.160.14822 MSI. You may have to register first. Install the software.

Now open (add to the solution if you did not yet do so) the Web application and add some references:

I created the following folder structure:

Let's first create the interface for the Unit of Work:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
namespace Web.Infrastructure
{
public interface ISession:IDisposable {
void CommitChanges();
Db4objects.Db4o.IObjectContainer Container { get; }
void Delete<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression);
void Delete<T>(T item);
void DeleteAll<T>();
void Dispose();
T Single<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression);
System.Linq.IQueryable<T> All<T>();
void Save<T>(T item);
}
}

And since we'll be using Db4o for our objectpersistence, let's create a Db4oSession, that derives from ISession.

using Db4objects.Db4o;
using System.Linq;
using Db4objects.Db4o.Linq;
using System.Web;
using System.IO;
using System;
using System.Collections.Generic;
namespace Web.Infrastructure
{
public class Db4oSession : IDisposable, ISession
{
private IObjectContainer db;
//private IObjectServer _server;
public IObjectContainer Container
{
get
{
return db;
}
}
public Db4oSession(IObjectServer server)
{
db = server.OpenClient();
}
/// <summary>
/// Returns all T records in the repository
/// </summary>
public IQueryable<T> All<T>()
{
return (from T items in db
select items).AsQueryable();
}
/// <summary>
/// Finds an item using a passed-in expression lambda
/// </summary>
public T Single<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression)
{
return All<T>().SingleOrDefault(expression);
}
/// <summary>
/// Saves an item to the database
/// </summary>
/// <param name="item"></param>
public void Save<T>(T item)
{
db.Store(item);
}
/// <summary>
/// Deletes an item from the database
/// </summary>
/// <param name="item"></param>
public void Delete<T>(T item)
{
db.Delete(item);
}
/// <summary>
/// Deletes subset of objects
/// </summary>
public void Delete<T>(System.Linq.Expressions.Expression<Func<T, bool>> expression)
{
var items = All<T>().Where(expression).ToList();
items.ForEach(x => db.Delete(x));
}
/// <summary>
/// Deletes all T objects
/// </summary>
public void DeleteAll<T>()
{
var items = All<T>().ToList();
items.ForEach(x => db.Delete(x));
}
/// <summary>
/// Commits changes to disk
/// </summary>
public void CommitChanges()
{
//commit the changes
db.Commit();
}
public void Dispose()
{
//explicitly close
db.Close();
//dispose 'em
db.Dispose();
}
}
}

Next, we need to set up the mechanism that handles the connection to the database for us. This is the SessionFactory:

using Db4objects.Db4o;
using System.Linq;
using Db4objects.Db4o.Linq;
using System.Web;
using System.IO;
using System;
using System.Collections.Generic;
namespace Web.Infrastructure {
public class SessionFactory
{
static ISession _current;
//this needs to stay static - can't have more than
//one server on the file
static IObjectServer _server;
public static ISession CreateSession()
{
if (_server == null)
{
_server = Db4oFactory.OpenServer( @"c:\temp\inventoryDb", 0);
}
return new Db4oSession(_server);
}
public static Db4oSession Current
{
get
{
if (_current == null)
_current = CreateSession();
return (Db4oSession) _current;
}
}
}
}

Now, we are ready to write our first tests:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using NUnit.Framework;
using Web.Infrastructure;
using Core;
namespace Test
{
[TestFixture]
public class PersistenceTest
{
[Test]
public void DB_Should_Save_Multiple_Nested_Objects()
{
using (var s = SessionFactory.Current)
{
s.DeleteAll<BladeServer>();
s.DeleteAll<BladeChassis>();
var bc = new BladeChassis { ChassisID = "1", UnitsHigh = 8 };
var bs = new BladeServer { Hostname = "SRV01", IPAddress = "10.10.10.12", OS = "Server 2008 R2", ServerRole = "Domain Controller", BladeChassis = bc };
var bs1 = new BladeServer { Hostname = "SRV02", IPAddress = "10.10.10.11", OS = "Server 2008 R2", ServerRole = "Exchange 2010", BladeChassis = bc };
List<BladeServer> servers = new List<BladeServer>();
servers.Add(bs);
servers.Add(bs1);
bc.Servers = servers;
s.Save(bc);
s.CommitChanges();
var server = s.Single<BladeServer>(x => x.Hostname == "SRV02");
Assert.NotNull(server);
Assert.AreEqual(2, s.All<BladeServer>().Count());
Assert.AreEqual(1, s.All<BladeChassis>().Count());
}
}
}
}

I can run them successfully in Nunit, which is awesome.

And yes, this all is based on the weblog of my hero Rob Conery. I am so sorry.

Well, we're almost there. We can actually write our controller methods now. Let's add a BladeChassisController and add the Index method:

1
2
3
4
5
6
public ActionResult Index()
        {
            var chassis = SessionFactory.Current.All<BladeChassis>();
            return View(chassis);
             
        }

And this would be our Edit method (that retrieves an instance of the bladechassis to edit):

1
2
3
4
5
6
public ActionResult Edit(int id)
        {
            string cid = id.ToString();
            var chassis = SessionFactory.Current.Single<BladeChassis>(x => x.ChassisID == cid);
            return View(chassis);
        }

And this is the actual altering the 'record' in the 'database':

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[HttpPost]
        public ActionResult Edit(int id, FormCollection values)
        {
            try
            {
                // TODO: Add update logic here
                string cid = id.ToString();
                var c = SessionFactory.Current.Single<BladeChassis>(x => x.ChassisID == cid);
                 
                UpdateModel(c);
                SessionFactory.Current.Save(c);
                SessionFactory.Current.CommitChanges();
                return RedirectToAction("Index");
            }
            catch
            {
                return View();
            }
        }

And here is the delete method:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public ActionResult Delete(int id)
        {
            try
            {
                string cid = id.ToString();
                var chassis = SessionFactory.Current.Single<BladeChassis>(x => x.ChassisID == cid);
                SessionFactory.Current.Delete(chassis);
                return RedirectToAction("Index");
            }
            catch
            {
                return View("Index");
            }
        }

It's insanely simple. (Until you need to fill a DropDownList, but that's a separate article).

You can download the example here.
Don't forget to run the Console App in Tasks first, because it adds some data to the app.