DataContextBuilder

GoodStuff includes a simple pattern for working with Linq2Sql. One of the core fundamentals of this pattern is that the data context should be closed as soon as possible, and that every access to the database needs to be isolated and tuned to save on resources. Here are some of the principles behind the datacontext builder pattern:
  • Force the use of a pattern that closes access to the database connection by applying a using() pattern.
  • Inline declaration of LoadWith statements so they are specifically tailored to the data usage of the method.
  • Shortcuts to frequently used LINQ patterns like Where, SingleOrDefault, DeleteOnSubmit etc.
  • Very little code to keep it readable.

The following code fragment shows the datacontext pattern in its simplest form. We assume that you have a .dbml file that contains a simple class named person with two properties Id and Name.

using(var db = new DataContextBuilder().Build())
{
     return db.SingleOrDefault<Person>(p => p.Id == 4);
}

This will return a person-record (if it exists) with Id 4.

The sample above uses the default constructor of the DataContextBuilder, that will assume you have a connectionString section in your web- or app.config named 'Database'. If you want to provide for another connection string section, you can use the overload:

.... new DataContextBuilder("alternativeDatabase") ...

The pattern uses a Fluent builder to provide for different access methods on the database, e.g. transaction support. This is the main reason you will want to use a var construct instead of a specific interface name. The intellisense support for the builder will only provide the features of the datacontext that you have provided.

Updating records

If you need to modify data in the database, you will need to enable object tracking on the builder:

using(var db = new DataContextBuilder()
    .EnableObjectTracking()
    .Build())
{
     Person newPerson = new Person() { Name = "John Doe" };
  
     db.Insert<Person>(newPerson);
     db.SubmitChanges();
}

Using transactions

On step further is to enable transactions on the datacontext. Because this pattern is used rather frequently, we decided to shortcut the BeginTransaction() method with the Build() method, so you don't have to specify both.

using(var db = new DataContextBuilder()
    .EnableObjectTracking()
    .BeginTransaction())
{
     try
     {
         Person newPerson = new Person() { Name = "John Doe" };
  
         db.Insert<Person>(newPerson);
         db.SubmitChanges();
         db.Commit();
    }
    catch
    {
         db.RollBack();
         throw;
     }
}

LoadWith<> support

If you are in ReadOnly mode (that is: don't enable ObjectTracking), there is no way to reach related objects. You must specify load options. The builder pattern supports this as in the following example:

using(var db = new DataContextBuilder()
     .LoadWith<Person>(p => p.Address)
     .Build())
{
     return db.SingleOrDefault<Person>(p => p.Id == 4);
}

Shortcut overloads on the db class.


// return the ITable<Person> of the datacontext
db.Select<Person>();     

// avoid .Select<Person>().InsertOnSubmit()
db.InsertOnSubmit(new Person());

// avoid .Select<Person>().Where(..)
db.Where<Person>(p => p.Id > 8);

// avoid .Select<Person>().SingleOrDefault()
db.SingleOrDefault<Person>(p => p.Id == 4);

db.Single<Person>()

Generated DataContext class

Somebody mentioned the fact that this pattern ignores the generated DataContext class with the .dbml file. That is true, this pattern only uses the generated entity classes. One of the drawbacks of this approach is that you cannot use the configured stored procedures. We hardly use stored procedures in combination with Linq2Sql so we think it is not an issue.

Another drawback of the generated datacontext class is that it comes with a multitude of constructors that you very rarely want to use, and that it usually leads to misuse of the connection string parameters. By providing a connectionStringName argument to the datacontext builder we are sure the same pattern is used throughout.

Note that the generated datacontext class just uses a simple overload to reach root object, e.g.

public IQueryable<Person> Persons
{ 
    get { return this.GetTable<Person>(); 
}
If this is the only benefit of the generated datacontext class we choose to ditch it.

Last edited Oct 6, 2011 at 1:45 PM by drblame, version 1

Comments

No comments yet.