LINQ + WCF + Silverlight (Part One)
Share
After spending a respectable amount of time building LINQ to SQL web applications, I’ve uncovered a set of practices that makes it much easier for me to wrap a SQL Server table model around data classes and keep each aspect of my layered application properly separated. This means my data layer is testable and separate from my business (or domain) layer, which is testable and separate from my presentation layer. With this pattern in place, creating a flexible, reusable solution for all of your layered web applications is much easier, even if you decide not to use LINQ to SQL for your data persistence.
The goal
Overall, the goal of this exercise to build a starting point that is very easy adapt to new projects. We should be able to get from a project-specific data model to a layered architecture for accessing objects in the model, from the database to the Silverlight client through WCF services, in very little time.
In this series we will touch on a few concepts that, due to the nature of LINQ to SQL to generate its entities dependent on the data layer they reside in, are necessary to achieve true separation. I won’t assume that you’ve spent a great deal of time with mocking or dependency injection, so we’ll use simple examples: if you have experience with them, you can think of this pattern in your own terms, and if you haven’t, you’ll get a bit of exposure which might inspire you to learn more.
The data layer
First let’s get an idea of how clean and compact our data layer code can be using this pattern. Here is a (nearly) complete data layer implementation for the sample AdventureWorks database for SQL Server 2005. There are only two classes in this project, other than the generated ‘entities’ for your data model, which are added when you create new LINQ to SQL classes: AdventureWorksRepository and AdventureWorksUnitOfWork. These classes implement the pattern you’ll learn and are very lightweight. Working from the top down, here are the details of these pattern classes.
using Dimebrain.Patterns.Data.Interfaces;
using Dimebrain.Patterns.Data.Linq;
namespace Dimebrain.Patterns.Web.Demo.AdventureWorks.DataLayer
{
public class AdventureWorksRepository<T> : LinqSqlRepository<T, LinqSqlDataSource<T>>
where T : class, IDataTransferObject
{
#region Overrides of LinqSqlRepository<T,LinqSqlDataSource<T>>
public override ILinqSqlUnitOfWork<T, LinqSqlDataSource<T>> UnitOfWork
{
get { return new AdventureWorksUnitOfWork<T>(); }
}
#endregion
}
}
using System.Configuration;
using System.Data.Linq;
using Dimebrain.Patterns.Data.Interfaces;
using Dimebrain.Patterns.Data.Linq;
namespace Dimebrain.Patterns.Web.Demo.AdventureWorks.DataLayer
{
public class AdventureWorksUnitOfWork<T> : LinqSqlUnitOfWork<T, LinqSqlDataSource<T>>
where T : class, IDataTransferObject
{
private LinqSqlDataSource<T> _dataSource;
#region Overrides of LinqSqlUnitOfWork<T,AdventureWorksDataContext>
/// <summary>
/// The connection string name to refer when making a new connection.
/// </summary>
public override string ConnectionStringName
{
get { return "AdventureWorksConnectionString"; }
}
/// <summary>
/// The key used to store a <see cref="DataContext" />.
/// </summary>
public override string ContextKey
{
get { return "AdventureWorksDb"; }
}
/// <summary>
/// Returns a new instance of this unit of work. If a new instance
/// is requested in the same scope, the <see cref="IDataSource{T}" />
/// should be preserved.
/// </summary>
public override ILinqSqlUnitOfWork<T, LinqSqlDataSource<T>> Instance
{
get { return new AdventureWorksUnitOfWork<T>(); }
}
/// <summary>
/// Creates a new instance of the <see cref="DataContext" /> to perform
/// operations against. The new instance is stored based on the scope of
/// the base unit of work.
/// </summary>
/// <returns></returns>
public override LinqSqlDataSource<T> CreateNewDataSource()
{
var cs = ConfigurationManager.ConnectionStrings[ConnectionStringName].ToString();
return new LinqSqlDataSource<T>(new AdventureWorksDataContext(cs));
}
/// <summary>
/// The current <see cref="LinqSqlDataSource{T}" /> scoped to the inheritor's design.
/// </summary>
public override LinqSqlDataSource<T> DataSource
{
get { return _dataSource ?? (_dataSource = CreateNewDataSource()); }
set { _dataSource = value; }
}
#endregion
}
}
This class has a little more meat but it’s essentially light as well. This is the unit of work we mentioned in our first repository class. This unit of work is aware of a LINQ to SQL connection string that is used to construct a new LinqSqlDataSource<T>. It also contains a key since it’s possible to scope a data source depending on your needs. For example, most ASP.NET web applications scope a LINQ to SQL DataContext to the HttpRequest. In other words, a new DataContext is created for each HttpRequest and thrown away when the request ends. This provides the best balance of concurrency tracking (the context doesn’t go stale) and performance (we’re reusing the connection for every database action in the request). This may or may not be a part of your design, and if you’re testing you may want to avoid an HttpRequest completely, so we should have multiple ways of providing a data source.
Working backwards
From a high level, that’s all the code necessary to create a functional data layer. Let’s take a look at two concepts we’re introducing, a “repository” and a “unit of work”.
“repository“
A repository is an abstract way of representing interactions with a database, which is a really specific kind of repository (my gym bag is another repository). When we define storing objects in a repository rather than a SQL Server 2005 database, we’re able to implement alternative versions in the future without breaking our code. In this case we’re storing our objects in a repository via LINQ to SQL. That is why you’ll notice that our AdventureWorks repository derives from LinqSqlRepository and not GymBagRepository.
“unit of work“
A unit of work is helpful when working with LINQ to SQL and other object-relational mapping tools because these tools tend to follow a pattern where they retrieve a snapshot of data from a database at a fixed point in time, perform changes to that data (through the class instances in code), and then compare the changed objects with what’s currently present in the database. The longer you hold on to that original snapshot of data without returning it to the database, the greater the chance that someone else will come along and change the real data, making your snapshot stale.
This snapshot latency presents a host of challenges, as now your persistence solution will need to decide whose data “wins”: yours, when you submit the changes, or hers, who submitted changes ahead of you. It’s also more difficult to tell what objects your snapshot really contains, now that they may no longer resemble what they used to be, or even exist at all. So, a unit of work for our purposes is the minimum effort required before we can submit our changes and keep the database healthy.
Wrap up
So we’ve caught a glimpse of what we have in mind for the data layer. If we build it right, we’ll have a testable, repeatable way to turn any data model into a functional LINQ to SQL data layer, with as much code as you saw above (okay, a tiny bit more) and as little manual work as possible. Next time we’ll explore what’s underneath these classes and how to test them.
Note: If you’re just getting into the concepts of repositories, units of work, entities, and data transfer objects, don’t worry. LINQ to SQL calls any class that it generates data access code for an ‘entity’, but that might be misleading if you’ve read about ‘Domain-Driven Design’. In that light, an entity should have nothing to do with data access, and is really your plain objects with behaviours acting out the dance of your problem domain (the business process you’re trying to illustrate and solve in code); in this case LINQ to SQL’s “entity” is really a “data transfer object”, or a container for moving the values of the database around in a class instance. So I may use the term “entity” in the data layer but I really mean “data transfer object”, which is precisely why you see the IDataTransferObject interface in the code above. All that’s doing is reminding us that the classes using this code are the classes LINQ to SQL generated for us. In fact, the “tiny bit more” code we haven’t seen yet are the partial classes for every LINQ to SQL “entity” that we’re using to make each one implement IDataTransferObject. Here’s one:
using Dimebrain.Patterns.Data.Interfaces;
namespace Dimebrain.Patterns.Web.Demo.AdventureWorks.DataLayer.Entities
{
public partial class Customer : IDataTransferObject {}
}
Article roadmap: LINQ + WCF + Silverlight
Download: Full solution
Socialized