LINQ + WCF + Silverlight (Part Two)


Share

Last time we began at the end of the data layer implementation, showing that our goal here is to set up a foundation where we can easily swap the specific database bits in a new project. This time we’ll start at the beginning with the Core project. Often our goal is to build up a solution out of projects that know as little as possible about each other. One way to help this is to begin with a ‘Core’ project that contains classes and interfaces that make sense for all projects in the solution.

Keeping layers separate

EntityNamespaceLINQ to SQL runs into an issue right away with classic layered approaches because in order to use the generated classes, you must reference an entity namespace. This isn’t such a serious issue, but if you intend to use these classes as your application’s domain classes, and you’re storing all of your data functionality in a data layer, then you must reference your data layer in any other part of your application that wants to use your classes (I’d imagine that would be all of them). Luckily for us we have a “get out of dependency free” card because Silverlight must talk to our data classes through WCF, which means it will generate its own proxies to our data layer classes (we’re getting ahead of ourselves, but the ‘Unidirectional’ mode set in the designer above will decorate all of those classes with the necessary WCF-enabling attributes), the end result is our presentation (Silverlight) won’t have any reference to our other layers, only a service contract to speak to one of them.

The core data classes

Our specific code for AdventureWorks inherited a number classes we haven’t seen, and these exist in the Core project. The reason they live here and not in the data layer is because that data layer should be specific to our application; the reusable stuff can live in the Core, where we can call on it from another data layer if our needs change. The classes we have yet to discover are LinqSqlUnitOfWork, LinqSqlRepository, and LinqSqlDataSource. Simply enough, each of these are specific to LINQ to SQL behind the scenes and they are categories of units of work, repositories, and data sources. Let’s start at the repository, which is our type-specific way of storing and retrieving data objects.

IRepository

The interface here simply defines the repository as a collection of typical methods for working with our data classes. The method names are very similar to LINQ to make it familiar, and we’ll pass LINQ queries into some of these methods to help keep our interface “composable”, which just means we don’t have to have a method for every specific thing we want to do, we can pass in lambda expressions to describe the action or criteria we want to perform as a query. For example, the Update method can take a query so we only update the entities in the repository that fit a particular description you provide as runtime.

using System;
using System.Collections.Generic;
 
namespace Dimebrain.Patterns.Data.Interfaces
{
    /// <summary>
    /// This is a generic contract that describes how callers will manage DTOs
    /// in the application. It is structured to feel familiar to LINQ, and is
    /// composable through predicates.
    /// </summary>
    /// <typeparam name="T">The entity type.</typeparam>
    public interface IRepository<T> : IPersistable<T> where T : IDataTransferObject
    {
        IEnumerable<T> All();
 
        IEnumerable<T> Where(Func<T, bool> query);
 
        T Single(Func<T, bool> query);
 
        T SingleOrDefault(Func<T, bool> query);
 
        T First(Func<T, bool> query);
 
        T FirstOrDefault(Func<T, bool> query);
 
        void SubmitChanges();
    }
}

Code Listing – IRepository.cs

Half the story is the IRepository<T> interface that describes operations against data classes already stored in the implementor. These are specific to a repository, but a repository can also provide persistence, so it in turn implements IPersistable<T>.

using System;
using System.Collections.Generic;
 
namespace Dimebrain.Patterns.Data.Interfaces
{
    /// <summary>
    /// Represents the ways a <see cref="IDataTransferObject" /> can persist
    /// itself to an underlying data store.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    public interface IPersistable<T> where T : IDataTransferObject
    {
        T Create();
 
        bool Insert(T entity);
 
        bool InsertAll(IEnumerable<T> entities);
 
        bool Delete(T entity);
 
        bool DeleteAll(Func<T, bool> query);
 
        T Update(T entity, Action<T> update);
 
        IEnumerable<T> UpdateAll(Func<T, bool> query, Action<T> update);
 
        T Update(T entity);
    }
}

Code Listing – IPersistable.cs

Now we can use these interfaces to provide a repository for any IDataTransferObject (in this case, classes generated by LINQ to SQL from a database, but they could come from anywhere). Let’s look at the LINQ to SQL flavour of the IRepository<T> interface.

ILinqSqlRepository

using System.Data.Linq;
using Dimebrain.Patterns.Data.Interfaces;
using Dimebrain.Patterns.Data.Linq;
 
public interface ILinqSqlRepository<T, K> : IRepository<T>
    where T : class, IDataTransferObject
    where K : LinqSqlDataSource<T>
{
    Table<T> Table { get; }
    ILinqSqlUnitOfWork<T, K> UnitOfWork { get; }
}

Code Listing – ILinqSqlRepository.cs

LINQ to SQL has the concept of a generic Table class, and since LINQ to SQL operates on a DataContext class that has intimate knowledge of your generated entities, we need a way to reference that as well, which we’ll accomplish through the UnitOfWork. We’re using another interface and hiding the DataContext inside a UnitOfWork because we want to be able to test our implementations of these contracts. Since the UnitOfWork could be anything, we can substitute an in-memory, helper object rather than a real DataContext, so we don’t need to have a database instance handy to run unit tests.

using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using Dimebrain.Patterns.Data.Interfaces;
using Dimebrain.Patterns.Data.Linq;
 
namespace Dimebrain.Patterns.Data.Linq
{
    /// <summary>
    /// A LINQ to SQL specific implementation for the <see cref="IRepository{T}" /> contract.
    /// </summary>
    /// <typeparam name="T">The entity type</typeparam>
    /// <typeparam name="K">The data source</typeparam>
    public abstract class LinqSqlRepository<T, K> : DisposableBase, ILinqSqlRepository<T, K>
        where T : class, IDataTransferObject
        where K : LinqSqlDataSource<T>
    {
        #region Implementation of ILinqSqlRepository<T>
 
        public Table<T> Table
        {
            get { return UnitOfWork.DataSource.Table; }
        }
 
        public abstract ILinqSqlUnitOfWork<T, K> UnitOfWork { get; }
 
        #endregion
 
        #region Implementation of IPersistable<T>
 
        public T Create()
        {
            var entity = Activator.CreateInstance<T>();
            Table.InsertOnSubmit(entity);
            return entity;
        }
 
        public bool Insert(T entity)
        {
            try
            {
                Table.InsertOnSubmit(entity);
                return true;
            }
            catch
            {
                return false;
            }
        }
 
        public bool InsertAll(IEnumerable<T> entities)
        {
            try
            {
                Table.InsertAllOnSubmit(entities);
                return true;
            }
            catch
            {
                return false;
            }
        }
 
        public bool Delete(T entity)
        {
            try
            {
                UnitOfWork.Delete(entity);
                return true;
            }
            catch
            {
                return false;
            }
        }
 
        public bool DeleteAll(Func<T, bool> query)
        {
            try
            {
                UnitOfWork.Delete(Where(query));
                return true;
            }
            catch
            {
                return false;
            }
        }
 
        public T Update(T entity, Action<T> update)
        {
            return UnitOfWork.Playback(entity, update);
        }
 
        public IEnumerable<T> UpdateAll(Func<T, bool> query, Action<T> update)
        {
            return UnitOfWork.Playback(Where(query), update);
        }
 
        public T Update(T entity)
        {
            return UnitOfWork.Map(entity);
        } 
        #endregion
 
        #region Implementation of IRepository<T>
 
        public IEnumerable<T> All()
        {
            return Table;
        }
 
        public IEnumerable<T> Where(Func<T, bool> query)
        {
            return Table.Where(query);
        }
 
        public T Single(Func<T, bool> query)
        {
            return Table.Single(query);
        }
 
        public T SingleOrDefault(Func<T, bool> query)
        {
            return Table.SingleOrDefault(query);
        }
 
        public T First(Func<T, bool> query)
        {
            return Table.First(query);
        }
 
        public T FirstOrDefault(Func<T, bool> query)
        {
            return Table.FirstOrDefault(query);
        }
 
        public void SubmitChanges()
        {
            UnitOfWork.SubmitChanges();
        } 
        #endregion
    }
}
Code Listing – LinqSqlRepository.cs
 
The concrete implementation of ILinqSqlRepository<T> passes most of its responsibilities to the Table class that is obtained through the UnitOfWork’s IDataSource<T>. At its heart, LinqSqlRepository is passing our composable queries to the Table and calling them as if we had gotten hold of the DataContext for our database explicitly. Next time we’ll take a closer look at the unit of work and data source concepts to round out our understanding of the reusable data layer.
 

Article roadmap: LINQ + WCF + Silverlight
Download: Full solution

 
Technorati Tags: ,,,
Kick It on DotNetKicks.com
blog comments powered by Disqus