Db4o and ASP.NET MVC 2 part 1

Today Scott Guthrie anounced the final release of ASP.NET MVC 2. So it’s a good time to give this a spin and check out the enhancements, like the new model validation and asynchronous controllers.

The best way to learn is to actually build an application. That’s what I would like to do here. I am going to build a blog application. For persistence I will use db4o, an object oriented database. Reasons for using Db40 is convenience. With Db4o, you don’t need to run SQL Server, which makes your code very portable and extremely easy to adjust to changing businessneeds.

The way I code this application is probably not from the books. It’s just how I think I should code. If you can think of any improvements please do not hesitate and tell us.

IkZeg
I’m going to call this application ‘IkZeg’. It’s a blog application, so we need a class for stories and for writers. We also like to enable comments. The classdiagram would be something like this:
ClassDiagram2

Now install ASP.NET MVC 2 and download Db40 for .NET 3.5

Start Visual Studio (2008, I won’t use 2010 until it’s RTM-ed) and create a new ASP.NET MVC 2 Web Application: IkZeg.

start

I’m not adding a Test project because I just want to get going as fast as possible.

Then, rightclick the folder Models and add a new class and name it: Story.

public partial class Story
    {
        public Story()
        {
            storyId = System.Guid.NewGuid();
            commentsCount = 0;
            storyDate = DateTime.Now;
        }
        public Guid storyId { get; set; }
        public string storyTitle { get; set; }
        public string storyText { get; set; }
        public DateTime storyDate { get; set; }
        public Writer writer { get; set; }
        public int commentsCount { get; set; }
        public List

Also add a Writer and a Comment class:

 public class Writer
    {
       public Writer()
       {
           registerDate = DateTime.Now;
       }
        public string username { get; set; }
        public string password { get; set; }
        public string email { get; set; }
        public DateTime registerDate { get; set; }
        //the birthday is nullable:
        public DateTime? birthday { get; set; }
    }

public class Comment
    {
        public Comment()
        {
            postedDate = DateTime.Now;
        }
        public string username { get; set; }
        public string commentOn { get; set; }
        public DateTime postedDate { get; set; }
        public string commentText { get; set; }
    }

Now that we’ve created the model it’s time to dive into persistence with Db4o.

Data Access with Db4o

An object-oriented database stores objects directly in the database without having to map it to a relational structure. This is a major timesaver and it has lots of other advantages (and disadvantages) but that discussion is a bit out of scope. Db4o is just awesome to use.

Add references to dll’s from Db4o: Db40objects.Db4o, Db40objects.Db4o.CS and Db40objects.Db4o.Linq:

ref

To implement persistence I followed Rob Conery because yes, he’s my hero. I learn an awful lot from him and from the Tekpub vids.

Data Access

To get started with Db4o is really simple. After adding the references you can start doing CRUD operations immediately, like so:

using(IObjectContainer db = Db4oEmbedded.OpenFile(Db4oEmbedded.NewConfiguration(),@"c:\temp\thebigblahdatabase.dat"))
{
    Story story = new Story();
    story.storyTitle="Wat doen we met Pasen?!"
    story.storyText="Codekloppen, obviously!"
    db.store(story);
    db.Close();
}

But that would mean we would be repeating ourselves continuously. With every CRUD operation, we would have to write the same piece of code and worry about closing the database. It would be nice to abstract these operations away. Also, maybe we would like to migrate to MySQL or Oracle later. So let’s take another approach.

Unit of Work

We’ll set up a Unit of Work. This takes care of CRUD methods. The value of using a Unit of Work pattern is to free the rest of your code from these concerns so that you can concentrate on business logic.

DataAccess

using Db4objects.Db4o;
using Db4objects.Db4o.Linq;
using Db4objects.Db4o.CS;

namespace IkZeg.Models
{
    public interface ISession:IDisposable 
    {
        void CommitChanges();
        Db4objects.Db4o.IObjectContainer Container { get; }
        void Delete(System.Linq.Expressions.Expression> expression);
        void Delete(T item);
        void DeleteAll();
        void Dispose();
        T Single(System.Linq.Expressions.Expression> expression);
        System.Linq.IQueryable All();
        void Save(T item);
    }
}

There’s a lot of funky generics in there. That’s good, because we need to implement this interface for all our ikZeg classes (Story, Writer and Comment).

Now let’s create a class that implements the ISession interface we just created:

using Db4objects.Db4o;
using Db4objects.Db4o.Linq;
using Db4objects.Db4o.CS;

namespace IkZeg
{
    public class Db4oSession : IDisposable,ISession
    {
        private IObjectContainer db;
        public IObjectContainer Container {
            get {
                return db;
            }
        }

        public Db4oSession(IObjectServer server) {
            db=server.OpenClient();
        }

       /// 
        /// Returns all T records in the repository
        /// 
        public IQueryable All() {
            return (from T items in db
                    select items).AsQueryable();
        }
/// 
        /// Finds a subset of items using a passed-in expression lambda
        /// 
        public IQueryable Where(System.Linq.Expressions.Expression> expression)
        {
            return All().Where(expression);
        }
 
        
        /// 
        /// Finds an item using a passed-in expression lambda
        /// 
        public T Single(System.Linq.Expressions.Expression> expression) {
            return All().SingleOrDefault(expression);
        }
 
        /// 
        /// Saves an item to the database
        /// 
        /// 
        public void Save(T item) {
            db.Store(item);
        }
 
        /// 
        /// Deletes an item from the database
        /// 
        /// 
        public void Delete(T item) {
            db.Delete(item);
        }
 
        /// 
        /// Deletes subset of objects
        /// 
        public void Delete(System.Linq.Expressions.Expression> expression) {
            var items = All().Where(expression).ToList();
            items.ForEach(x => db.Delete(x));
        }
 
        /// 
        /// Deletes all T objects
        /// 
        public void DeleteAll() {
            var items = All().ToList();
            items.ForEach(x => db.Delete(x));
        }
 
 
        /// 
        /// Commits changes to disk
        /// 
        public void CommitChanges() {
            //commit the changes
            db.Commit();
 
        }


        public void Dispose() {
            //explicitly close
            db.Close();
            //dispose 'em
            db.Dispose();
        }
    }
}

       

Now create a class that inherits from Db4oSession and it all starts making sense:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Db4objects.Db4o;
using Db4objects.Db4o.Linq;
using Db4objects.Db4o.CS;

namespace IkZeg.Models
{
    public class IkZegSession : Db4oSession
    {
        internal IkZegSession(IObjectServer server) : base(server) { }

        public IQueryable stories
        {
            get
            {
                return All();
            }
        }


        public IQueryable storiesByWriter(string writer)
        {
            return Where(s => s.writer.ToString() == writer);
        }


        public Story getStory(string id)
        {
            return Single(s => s.storyId.ToString() == id);
        }

        public int getComments(string storyGuid)
        {
            return Where(c => c.commentOn == storyGuid).Count();
        }

       }
}

And here is the class that holds the Db40 file- and connectioninfo:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using Db4objects.Db4o;
using Db4objects.Db4o.CS;
using Db4objects.Db4o.Linq;

namespace IkZeg.Models
{
    public class SessionFactory
    {
        static ISession _current;
       
        static IObjectServer _server;

        public static ISession CreateSession()
        {
            if (_server == null)
            {
                _server = Db4oFactory.OpenServer( @"c:\temp\debigblahdatabase", 0);
            }

            return new IkZegSession(_server);
        }

        public static IkZegSession Current
        {
            get
            {
                if (_current == null)
                    _current = CreateSession();
                return (IkZegSession)_current;
            }
        }

    }
}

Let’s use this

So, let’s create a story and show it on the website. To do that, I’ll need a controller and a view. Let’s start with the controller.

Right click the folder Controllers and click Add Controller.
Name the Controller StoryController and add action methods. Like this:

contr2

Add using statements for Db4o and the Models folder:

using IkZeg.Models;
using Db4objects.Db4o;
using Db4objects.Db4o.Linq;
using Db4objects.Db4o.CS;

Just below the StoryController declare an IkZegSession:

  public class StoryController : Controller
    {
        IkZegSession db = SessionFactory.Current;

Scroll down to line 63 or so to the Create method and make it look like this:

   [HttpPost]
        public ActionResult Create(Story story)
        {
            if (ModelState.IsValid)
            {
                    story.writer = new Writer { username = Request.Form["writer.username"] };
                    story.storyDate = DateTime.Now;
                    db.Save(story);
                    return RedirectToAction("Index");
                }

                        else
            {
                return View();
            }
        }

Now add the view. Right click the Create Method (along line 65) and click Add View.

addView

Create a strongly-typed view. The data class is IkZeg.Models.Story. That’s the story class we made earlier.

view2

Almost done now. Let’s take a look a this View though. In the folder Views, you have now a Story folder, with a file named Create.aspx. Open the file and remove the HTML fieldset (because I think it’s ugly). Also remove the Guid labels and textboxes and the Postdate labels and textboxes. We don’t need them as they are generated in the Create method in the controller.

Hit debug (or F5). The webbrowser starts with the standard webpage. Browse to: http://localhost:51305(or whatever portnumber)/Story/Create. And there we are. It’s a bit ugly but we will work at the layout later.
cr

After clicking Create you’ll get a Yellow Screen of Death, which is okay, as we did not implement the Index method yet. I will cover that (and validation) in part 2.

10 Replies to “Db4o and ASP.NET MVC 2 part 1”

  • Hi,

    i’m currently looking into db4o myself, and regarding the “one instance only” problem, i wonder if you tried the IObjectServer approach instead of using IObjectContainer? That would – as far as i see – let you write a database server that multiple clients can connect to.

  • Hi Waaahsabi, thanks for you suggestion. I checked the IObjectServer approach out and it seems to result in much cleaner code. I think I’m going to add an extra db4o post into the series.

  • One thing i noticed with the whole object database concept is that i ran into problems with their Object Identity approach. The whole thing with identifying objects without keys or IDs is only working as long as the object doesn’t leave the database scope (f.e. getting transferred via WCF etc). As soon as an object DOES get transferred via a service, it looses connection (i.e. becomes inactivated). I was however able to write a mechanism that reconnects objects upon their return to the database scope.

    So until now i can say the object database concept holds up nicely. And it saves so much work, its unbelievable. Glad i don’t have to do data modeling twice anymore.

  • @Waaahsabi: Yes, ‘disconnected’ objects are a problem with db4o. What did you do to reconnect the objects? Added an id and mapped the changes between object (with Automapper)?

  • Hello,Awesome blog post dude! i am Tired of using RSS feeds and do you use twitter?so i can follow you there:D.
    PS:Do you thought to be putting video to the web site to keep the visitors more interested?I think it works., Bert Alicer

  • @Gamlor and @Waaahsabi you might wanna check over here : http://developer.db4o.com/Documentation/Reference/db4o-7.12/net35/reference/html/reference/platform_specific_issues/disconnected_objects/comparison_of_ids.html

    I prefer using GUID on the entities. Value objects don’t need ID’s. So not everything you store needs an ID.

    @Jacqueline, great blogpost(s). I just stumbled upon your blog when searching for ASP.NET MVC = db4o. I’m doing a pet project to see what the best practises are…

  • I am a frequent reader of your blog posts. I liked the recent one and other posts on your blog so much that I have subscribed to the blog’s RSS feed in Thunderbird. Even thinking of stealing some ideas and put them to work. Keep all the good work going by posting more informative posts. Thank you. Time well spent on this post.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: