August 18, 2008

LINQ + WCF + Silverlight (Part Three)

So far we've covered most of our data layer implementation with the exception of our use of a data source object to hide the details of the specific data implementation, in this case LINQ to SQL. If you recall, we introduced the concepts of a repository and a unit of work in the first article, but didn't look closely at the unit of work. It is not a special interface, other than declaring itself as IDisposable. This encourages the developer to use a unit of work in a 'using' scope, since we want to perform the unit of work, clean up, and move on to the next task.

using System;
 
namespace Dimebrain.Patterns.Interfaces
{
    /// <summary>
    /// A unit of work to perform.
    /// </summary>
    public interface IUnitOfWork : IDisposable {}
}

Code Listing - IUnitOfWork.cs

Since we really have no concept of the database in a unit of work we can extend the interface to provide some functionality that will build up to the data source concept. Remember that our goal here is to reduce the amount of application-specific code we need to write, and to fulfill our goal of writing a unit of work to perform database operations on a data source, and that we've so far built a repository that should encapsulate both the unit of work and the data source specific for LINQ to SQL. So let's move one step close and make the unit of work aware that it will perform operations against a database.

namespace Dimebrain.Patterns.Data.Interfaces
{
    /// <summary>
    /// A unit of work that requires a database provided by a connection name.
    /// </summary>
    public interface IDatabaseUnitOfWork<T, K> : IDataSourceUnitOfWork<T, K> 
        where T : class, IDataTransferObject                                 
        where K : IDataSource<T>
    {
        string ConnectionStringName { get; }
 
        void SubmitChanges();
    }
}

Code Listing - IDatabaseUnitOfWork.cs

Now we have a unit of work that is introducing two types, our regular class instance that implements IDataTransferObject (which all of our application-specific LINQ to SQL 'entities' will implement), and an IDataSource<T> interface that provides a means to speak to the collection of class instances we want to manipulate. Again, we are talking about small interfaces that help us abstract the players in the game, in case we want to change one of the components without changing how the rest of our system behaves. The IDataSource<T> is no exception.

using System.Linq;
 
namespace Dimebrain.Patterns.Data.Interfaces
{
    public interface IDataSource<T> : IQueryable<T> where T : class, IDataTransferObject
    {
        /// <summary>
        /// This method signals any transient data source to commit
        /// changes made since the last time this method was called.
        /// </summary>
        void SubmitChanges();
    }
}

Code Listing - IDataSource.cs

Borrowing from the LINQ to SQL method convention for consistency, our IDataSource<T> contract asks first that the data source derive from IQueryable<T>, and provides a method called SubmitChanges, just like the LINQ to SQL DataContext, to persist tracked in-memory changes back to the database. Now, not only can a proper LINQ to SQL DataContext live up to this contract, but so could any custom data source you provided, if you were targeting a different a database under the covers. IQueryable<T> is a special interface that allows us to build a query expression and then wait until the instance is enumerated before actually executing the query we've assembled against the database.

Now that we all of the participating interfaces, we can travel back to our LINQ to SQL specific implementation. If you recall, we have a LinqSqlRepository<T, K> class that takes its ILinqSqlUnitOfWork<T, K> and performs operations against its LinqSqlDataSource<T>: A repository, using a unit of work, to query a data source. Now that we have the interface, we can write the code for the LinqSqlDataSource<T>.

using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Linq;
using System.Linq.Expressions;
using Dimebrain.Patterns.Data.Interfaces;
 
namespace Dimebrain.Patterns.Data.Linq
{
    public class LinqSqlDataSource<T> : DataSourceBase<T>
        where T : class, IDataTransferObject
    {
        private readonly DataContext _context;
        private readonly Table<T> _table;
 
        private IQueryable<T> _queryable
        {
            get { return _table.AsQueryable(); }
        }
 
        public Table<T> Table
        {
            get { return _table; }
        }
 
        public DataContext Context
        {
            get { return _context; }
        }
 
        public LinqSqlDataSource(DataContext context)
        {
            _context = context;
            _table = _context.GetTable<T>();
        }
 
        #region Implementation of IDataSource<T>
 
        public override void SubmitChanges()
        {
            _context.SubmitChanges();
        }
 
        #endregion
 
        #region Implementation of IEnumerable
 
        public override IEnumerator<T> GetEnumerator()
        {
            return _table.GetEnumerator();
        }
 
        #endregion
 
        #region Implementation of IQueryable
 
        public override Expression Expression
        {
            get { return _queryable.Expression; }
        }
 
        public override Type ElementType
        {
            get { return _queryable.ElementType; }
        }
 
        public override IQueryProvider Provider
        {
            get { return _queryable.Provider; }
        }
 
        #endregion
    }
}
Code Listing - LinqSqlDataSource.cs

Since the LinqSqlDataSource must implement IDataSource<T>, IQueryable<T>, we need to provide all the methods from those interfaces, plus IEnumerable<T> (from IQueryable<T>). This turns out to be fairly easy since LINQ allows us to reconstitute IEnumerable<T>'s as IQueryable<T>'s with the AsQueryable extension method. In the code above, we're using LINQ to SQL's DataContext and Table<T> class to provide all of the functionality we're looking for. In the constructor, we're asking the developer to provide the LINQ to SQL DataContext that will drive this implementation. In short, we've wrapped the DataContext as a DataSource so that we can, if necessary, remove it completely without breaking our code.

Updating entities

The only piece of the data layer we have not seen is the LinqSqlUnitOfWork. This class is last in our discovery because it may not be clear how it is built. The reason is due to an often ignored peculiarity of LINQ to SQL when working in a multi-tier environment, which is exactly how we intend to use it. In brief, LINQ to SQL will not allow you to 'attach' an entity created elsewhere (on another layer) and persist it to the database in the current DataContext (whether it's scoped to the HttpRequest, as the example project for this series will provide through a bonus HttpLinqSqlUnitOfWork, or through some other scheme), unless you conform to a specific convention of adding a timestamp field to all tables which it will use for optimistic concurrency, fetch and clone the original state of the entity in order to compare it to the updated version, or use reflection to "map" the updates back to the original.

Previously I wrote about a way to get around all three of these workarounds by storing up the updates to an entity as a delegate, and "playing back" those changes on the server by sending the delegate down and unwrapping inside the DataContext. I really like this approach and I prefer it when working with web applications, but unfortunately WCF doesn't provide a way to serialize and send an Action delegate, so rather than create an elaborate scheme to persist the changes as a script, I've chosen to use the map approach to reflect the changes on the server. This may not be what you want to do, and you should alter this portion of the code to suit your needs. So, at last, here is the code for our LinqSqlUnitOfWork. We now have all of the generic core classes we used in the first part to build our application-specific LINQ to SQL data layer bits, which, if you recall, wasn't much effort at all.

using System;
using System.Collections.Generic;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Linq;
using System.Web;
using Dimebrain.Patterns.Data.Interfaces;
using Dimebrain.Patterns.Data.Linq;
 
namespace Dimebrain.Patterns.Data.Linq
{
    /// <summary>
    /// A LINQ to SQL-specific unit of work, allowing multi-tier updates 
    /// through either <see cref="Action"/>-based "playback", or reflection-based
    /// "mapping".
    /// </summary>
    /// <typeparam name="T">The entity type</typeparam>
    /// <typeparam name="K">The database context</typeparam>
    public abstract class LinqSqlUnitOfWork<T, K> : DisposableBase, 
                                                    ILinqSqlUnitOfWork<T, K>
                                                    where T : class, IDataTransferObject
                                                    where K : LinqSqlDataSource<T>
    {
        /// <summary>
        /// The connection string name to refer when making a new connection.
        /// </summary>
        public abstract string ConnectionStringName { get; }
 
        /// <summary>
        /// Submits pending changes for this unit of work.
        /// </summary>
        public void SubmitChanges()
        {
            DataSource.SubmitChanges();
        }
 
        /// <summary>
        /// The key used to store a <see cref="DataContext" />.
        /// </summary>
        public abstract string ContextKey { get; }
        
        /// <summary>
        /// Returns an 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 abstract ILinqSqlUnitOfWork<T, K> Instance { get; }
 
        /// <summary>
        /// Creates a new instance of the <see cref="IDataSource{T}" /> to perform
        /// operations against.
        /// </summary>
        /// <returns></returns>
        public abstract LinqSqlDataSource<T> CreateNewDataSource();
 
        #region ILinqSqlUnitOfWork<T,K> Members
 
        /// <summary>
        /// The current <see cref="LinqSqlDataSource{T}" /> scoped to the inheritor's design.
        /// </summary>
        public abstract K DataSource { get; set; } 
 
        /// <summary>
        /// Plays back changes made to an entity queued on another layer, and updates the
        /// database with the results.
        /// </summary>
        /// <param name="entity">The entity to play changes against</param>
        /// <param name="update">The delegate action to perform on the entity</param>
        /// <returns>The changed entity</returns>
        public T Playback(T entity, Action<T> update)
        {
            var db = DataSource;
 
            var table = db.Table;
            var original = Enumerable.FirstOrDefault(table, e => e == entity);
            if (original == null)
            {
                return original;
            }
 
            update(original);
            var updated = original;
            db.SubmitChanges();
 
            return updated;
        }
 
        /// <summary>
        /// Plays back changes made to a collection of entities queued on another
        /// layer, and updates the database with the results.
        /// </summary>
        /// <param name="entities">The entities to play changes against</param>
        /// <param name="update">The delegate action to perform on each entity</param>
        /// <returns>The changed entities</returns>
        public IEnumerable<T> Playback(IEnumerable<T> entities, Action<T> update)
        {
            var updated = new List<T>();
 
            var ds = DataSource;
            var table = ds.Table;
            var originals = table.Where(e => entities.Contains(e));
 
            foreach (var original in originals)
            {
                var item = Playback(original, update);
                if (item != null)
                {
                    updated.Add(item);
                }
            }
 
            ds.SubmitChanges();
            return updated;
        }
 
        /// <summary>
        /// Maps a changed entity back to the original and updates the database.
        /// </summary>
        /// <remarks>
        /// If you don't want to use reflection to perform the mapping, you can
        /// add a version-checking field to your tables and then use the Refresh method
        /// to update just the changed properties. I am using reflection for mapping
        /// here to avoid enforcing a timestamp convention on your data model.
        /// </remarks>
        /// <param name="entity">The changed entity</param>
        /// <returns>The synchronized entity</returns>
        public T Map(T entity)
        {
            var db = DataSource;
 
            // Get original version of entity
            var original = db.Table.FirstOrDefault(e => e == entity);
 
            // Perform the mapping from updated to original
            var updated = Map(entity, original);
 
            // Save changes to the original
            db.SubmitChanges();
 
            // Return the updated entity
            return updated;
        }
 
        /// <summary>
        /// Takes an updated and original entity and maps the updated properties
        /// back to the original entity.
        /// </summary>
        /// <remarks>
        /// This approach is necessary if we do not have a versioning column in the
        /// database, and because WCF won't accept delegates as parameters. This is why
        /// we can't perform the playback pattern in Silverlight scenarios.
        /// </remarks>
        /// <param name="updated">The updated entity</param>
        /// <param name="original">The original entity</param>
        /// <returns></returns>
        private static T Map(T updated, T original)
        {
            // Get table column properties
            var type = typeof (T);
            var columns =
                type.GetProperties().Where(
                    p => p.GetCustomAttributes(typeof(ColumnAttribute), false)
                        .FirstOrDefault() != null)
                        .AsEnumerable();
 
            // Perform the mapping from updated to original
            foreach