Linq To SQL : LinqContext per Unit of Work

11 11 2008

To implement a Linq To SQL Oriented solution, there are many ways to construct your application. But only one interested me : the Context per Unit of Work Architecture.

When using Linq To SQL, you need to instanciate a Context which contains your entities tables and allow you to apply query methods. But when your application is construct with many layers, you only can pass Context through methods to keep the same context during your business operations. It is the same problem when a transaction is required(managed by Linq To SQL Context though optimistic transaction).

When using the Unit of Work architecture proposed by Microsoft and few developpers, most of the common operation is easier. Then, I’ll explain the main idea :

I’ll suppose that we have a 2 layers application, called Web & BLL(contains DAL through Linq To SQL Schema).

We want to :

  • Work with a unique Linq To SQL Context during a HTTP Request(so only one per user request)
  • Access to the Context in Web layer(indirectly) and BLL layer(directly)
  • Denied access to the Context in code-behind

The first step is to create a controller class in the BLL layer to access to the context.

public class MyDataContext

{

public static IDataContextStorage MyDataContextStorage;

public static LinqContext Current

{

get

{

return MyDataContextStorage.Current;

}

set

{

MyDataContextStorage.Current = value;

}

}

public static void Save()

{

Current.SubmitChanges();

}

}

And create the following interface:

public interface IDataContextStorage

{

LinqContext Current { get; set; }

}

Now, we have a controller which can be called by the Web and BLL Layer. Accessing from web is not a good practice, so we can change to internal if we want to restrict access to BLL only and set the Current Property type return to object. However, I don’t like to cast each time I need my context, so a leave it like that.

MyDataContext is the wrapper to access to the Context. but we now need to store your context during your HTTP Request. We need to create the following class that allow us to store the context and access it for each user :

public class HttpDataContextStorage:IDataContextStorage

{

public MyBLL.LinqContext Current

{

get {

if ((MyBLL.LinqContext)HttpContext.Current.Items["DataContext"] == null)

HttpContext.Current.Items["DataContext"] = new MyBLL.LinqContext();

return (MyBLL.LinqContext)HttpContext.Current.Items["DataContext"]; }

set { HttpContext.Current.Items["DataContext"] = value; }

}

}

This class need to be placed in App_Code in Web layer(there are some HttpContext reference, so…).

For each application request, we are going to create a Context and destroy it when request is finished. Write in your global.asax.cs:

void Application_BeginRequest(object sender, EventArgs e)

{

MyDataContext.MyDataContextStorage = new HttpDataContextStorage();

}

void Application_EndRequest(object sender, EventArgs e)

{

MyDataContext.Current.Dispose();

}

Ok, all is defined.

when we need to execute a Linq to SQL Query, we only need to write :

MyDataContext.Current.Users.Where(u=>u.Id==1);

“Users” is a entity table for this sample.

When saving is required, do: MyDataContext.Save() or MyDataContext.Current.SubmitChanges()

With this strucutre, Linq Context is always available when you need it and allow to perform transaction through many methods calling.

However, some issues are known, as :

  • Serialization : If you do a full loading of Context, binding with associated object can provoke a serialization failure.
  • It is a HTTP Unit of work concept. If you want to perform asynchronous tasks, the Context will not be available. So keep in mind to build your MyDataContext Controller with features that allow custom Context loading.

This Linq To SQL Context Architecture is a simple sample to show the main idea. Sure, It can be improved to restrict the layers access, check if we use the HTTP Linq Context or a New Context for asynchronous tasks…

Reminder: A Linq Overview Ticket is available here to see others architectures and statistics.





View Linq SQL Queries in VS Debug mode

20 10 2008

When you begin to write complex Linq To SQL Queries with many IQueryable associations, it must be difficult to catch all the SQL generated parts by Linq To SQL. That’s why, after searching on the web some information, I have found this little class that can be usefull : DebugWriter.

Associate a new instance of DebugWriter with your LinqContext Log Property and all Linq To SQL Queries will be displayed in the VS Output during Debug Mode.

public class DebugWriter : TextWriter

{

private bool isOpen;

private static UnicodeEncoding encoding;

private readonly int level;

private readonly string category;

/// <summary>

/// Initializes a new instance of the <see cref=”DebuggerWriter”/> class.

/// </summary>

public DebugWriter()

: this(0, Debugger.DefaultCategory)

{

}

/// <summary>

/// Initializes a new instance of the <see cref=”DebuggerWriter”/> class with the specified level and category.

/// </summary>

/// <param name=”level”>A description of the importance of the messages.</param>

/// <param name=”category”>The category of the messages.</param>

public DebugWriter(int level, string category)

: this(level, category, CultureInfo.CurrentCulture)

{

}

/// <summary>

/// Initializes a new instance of the <see cref=”DebuggerWriter”/> class with the specified level, category and format provider.

/// </summary>

/// <param name=”level”>A description of the importance of the messages.</param>

/// <param name=”category”>The category of the messages.</param>

/// <param name=”formatProvider”>An <see cref=”IFormatProvider”/> object that controls formatting.</param>

public DebugWriter(int level, string category, IFormatProvider formatProvider)

: base(formatProvider)

{

this.level = level;

this.category = category;

this.isOpen = true;

}

protected override void Dispose(bool disposing)

{

isOpen = false;

base.Dispose(disposing);

}

public override void Write(char value)

{

if (!isOpen)

{

throw new ObjectDisposedException(null);

}

Debugger.Log(level, category, value.ToString());

}

public override void Write(string value)

{

if (!isOpen)

{

throw new ObjectDisposedException(null);

}

if (value != null)

{

Debugger.Log(level, category, value);

}

}

public override void Write(char[] buffer, int index, int count)

{

if (!isOpen)

{

throw new ObjectDisposedException(null);

}

if (buffer == null || index < 0 || count < 0 || buffer.Length – index < count)

{

base.Write(buffer, index, count); // delegate throw exception to base class

}

Debugger.Log(level, category, new string(buffer, index, count));

}

public override Encoding Encoding

{

get

{

if (encoding == null)

{

encoding = new UnicodeEncoding(false, false);

}

return encoding;

}

}

public int Level

{

get { return level; }

}

public string Category

{

get { return category; }

}

}

Sparing some time is the key to efficiency…