Click here to Skip to main content
Click here to Skip to main content

Implementing the Repository Pattern with LINQ-to-SQL

By , 4 Jun 2008
 

Introduction

The purpose of this article is to describe the technique I have used to implement the Repository pattern in .NET applications. I will provide a brief description of the Repository pattern and LINQ-to-SQL; however, if you are unfamiliar with these technologies, you should research them elsewhere. The goals of my implementation are:

  • it must be a general purpose design that can be reused for many projects
  • it must facilitate domain driven design
  • it must facilitate unit testing and testing in general
  • it must allow the domain model to avoid dependencies on infrastructure
  • it must provide strongly typed querying

Repository Pattern

The Repository Pattern, according to Martin Fowler, provides a "layer of abstraction over the mapping layer where query construction code is concentrated", to "minimize duplicate query logic". In practice, it is usually a collection of data access services, grouped in a similar way to the domain model classes.

By accessing repositories via interfaces, the Repository pattern helps to break the dependency between the domain model and the data access code. This is invaluable for unit testing because the domain model can be isolated.

I implement the Repository pattern by defining one repository class for each domain model entity that requires specialized data access methods (other than the standard create, read, update, and delete). If an entity does not require specialized data access methods, then I will use a generic repository for that entity. A repository class contains the specialized data access methods required for its corresponding domain model entity.

The following class diagram shows an example implementation with two domain entity classes: Shape and Vertex. Shape has a specialized repository (IShapeRepository). Vertex does not have a specialized repository, so it will just use the generic repository (IRepository<Vertex>).

Repository Diagram

LINQ-to-SQL

LINQ is a strongly typed way of querying data. LINQ-to-SQL is a dialect of LINQ that allows the querying of a SQL Server database. It also includes object / relational mapping and tools for generating domain model classes from a database schema. LINQ is an excellent addition to object / relational mapping tools because it facilitates strongly typed queries, such as:

IList<Shape> threeSidedShapes = 
  _genericShapeRepository.FindAll(shape => 
       shape.NumberOfSides == 3).Take(5).ToList();

IRepository<T>

The generic interface IRepository<T> defines the methods that are required on each repository.

public interface IRepository<T> where T : class
{
    /// <summary>
    /// Return all instances of type T.
    /// </summary>
    /// <returns></returns>
    IEnumerable<T> All();

    /// <summary>
    /// Return all instances of type T that match the expression exp.
    /// </summary>
    /// <param name="exp"></param>
    /// <returns></returns>
    IEnumerable<T> FindAll(Func<T, bool> exp);

    /// <summary>Returns the single entity matching the expression.
    /// Throws an exception if there is not exactly one such entity.</summary>
    /// <param name="exp"></param><returns></returns>
    T Single(Func<T, bool> exp);

    /// <summary>Returns the first element satisfying the condition.</summary>
    /// <param name="exp"></param><returns></returns>
    T First(Func<T, bool> exp);

    /// <summary>
    /// Mark an entity to be deleted when the context is saved.
    /// </summary>
    /// <param name="entity"></param>
    void MarkForDeletion(T entity);

    /// <summary>
    /// Create a new instance of type T.
    /// </summary>
    /// <returns></returns>
    T CreateInstance();

    /// <summary>Persist the data context.</summary>
    void SaveAll();
} 

Repository<T>

IRepository<T> is implemented by a generic repository base class, Repository<T>. Repository<T> is a base implementation that provides data access functionality for all entities. If an entity (T) does not require a specialized repository, then its data access will be done through Repository<T>.

public class Repository<T> : IRepository<T> 
    where T : class
{
    protected IDataContextFactory _dataContextFactory;

    /// <summary>
    /// Return all instances of type T.
    /// </summary>
    /// <returns></returns>
    public IEnumerable<T> All()
    {
        return GetTable;
    }

    /// <summary>
    /// Return all instances of type T that match the expression exp.
    /// </summary>
    /// <param name="exp"></param>
    /// <returns></returns>
    public IEnumerable<T> FindAll(Func<T, bool> exp)
    {
        return GetTable.Where<T>(exp);
    }

    /// <summary>See IRepository.</summary>
    /// <param name="exp"></param><returns></returns>
    public T Single(Func<T, bool> exp)
    {
        return GetTable.Single(exp);
    }

    /// <summary>See IRepository.</summary>
    /// <param name="exp"></param><returns></returns>
    public T First(Func<T, bool> exp)
    {
        return GetTable.First(exp);
    }

    /// <summary>See IRepository.</summary>
    /// <param name="entity"></param>
    public virtual void MarkForDeletion(T entity)
    {
        _dataContextFactory.Context.GetTable<T>().DeleteOnSubmit(entity);        
    }

    /// <summary>
    /// Create a new instance of type T.
    /// </summary>
    /// <returns></returns>
    public virtual T CreateInstance()
    {
        T entity = Activator.CreateInstance<T>();
        GetTable.InsertOnSubmit(entity);
        return entity;
    }

    /// <summary>See IRepository.</summary>
    public void SaveAll()
    {
        _dataContextFactory.SaveAll();
    }

    public Repository(IDataContextFactory dataContextFactory)
    {
        _dataContextFactory = dataContextFactory;
    }
    
    #region Properties

    private string PrimaryKeyName
    {
        get { return TableMetadata.RowType.IdentityMembers[0].Name; }
    }

    private System.Data.Linq.Table<T> GetTable
    {
        get { return _dataContextFactory.Context.GetTable<T>(); }
    }

    private System.Data.Linq.Mapping.MetaTable TableMetadata
    {
        get { return _dataContextFactory.Context.Mapping.GetTable(typeof(T)); }
    }

    private System.Data.Linq.Mapping.MetaType ClassMetadata
    {
        get { return _dataContextFactory.Context.Mapping.GetMetaType(typeof(T)); }
    }

    #endregion
}

IShapeRepository and ShapeRepository

It is usually desirable to provide more specialised repositories for entity classes. If our domain included a shape entity, we might like to have a ShapeRepository with a RetrieveByNumberOfSides(int sideCount) method. Such a class would be exposed to consumers as a specialized interface IShapeRepository:

public interface IShapeRepository : IRepository<Shape>
{
    Shape RetrieveByNumberOfSides(int sideCount)
}

public class ShapeRepository : Repository<Shape>, IShapeRepository
{
    public Shape RetrieveByNumberOfSides(int sideCount)
    {
        return FindAll(shape => shape.NumberOfSides == sideCount);
    }
}

Usage

We now have a fully functioning, decoupled repository implementation. A class might use the repositories as follows:

public class ApplicationService
{
    private IRepository<Shape> _genericShapeRepository;
    private IShapeRepository _specializedShapeRepository;

    public ApplicationService(IRepository<Shape> genericShapeRepository, 
                              IShapeRepository specializedShapeRepository)
    {
        _genericShapeRepository = genericShapeRepository;
        _specializedShapeRepository = specializedShapeRepository;
    }

    public void DoSomethingWithTheGenericRepository()
    {
        IList<Shape> threeSidedShapes = 
          _genericShapeRepository.FindAll(shape => shape.NumberOfSides == 3).ToList();

        _genericShapeRepository.MarkForDeletion(threeSidedShapes[0]);
        _genericShapeRepository.SaveAll();
    }

    public void DoSomethingWithTheSpecializedRepository()
    {
        IList<Shape> threeSidedShapes = 
          _specializedShapeRepository.RetrieveByNumberOfSides(3).ToList();

        _specializedShapeRepository.MarkForDeletion(threeSidedShapes[0]);
        _specializedShapeRepository.SaveAll();
    }

}

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

liammclennan
Web Developer
Australia Australia
Member
Liam McLennan has been developing for the internet since 2001. He is passionate about delivering top quality I.T. solutions that address his customer's needs.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralMy vote of 1memberjanus00731 Mar '09 - 9:39 
It has nothing to do with a real setup.
GeneralRe: My vote of 1memberswheat19 Apr '09 - 7:28 
Absolutely correct. This is great in theory. In practice it's useless.
GeneralRe: My vote of 1memberliammclennan19 Apr '09 - 10:51 
I think it is useful. Care to elaborate on your comment?
 
Liam McLennan
liam@eclipsewebsolutions.com.au
www.eclipsewebsolutions.com.au

GeneralRe: My vote of 1memberswheat19 Apr '09 - 16:45 
Firstly, thank you for not blasting me for sounding rude. Your code seems to be well thought out and well written. I have not actually stepped through it. My problem is not with your code, but with Linq to SQL, a technology I greatly enjoy but whose shortcomings give me great angst. L2S has great potential. Unfortunately, in its current implementation, as it relates to web applications, L2S is little more than a glorified pipe to a database. Dont get me wrong - There are a lot of things I like about L2S - namely entity class generation. But as far as functioning as a repository, L2S does not (for n tiered apps). The code you've written is good but in IMHO is written against a model that is not yet complete.
 
Here is a question I posed to Microsoft. You can read it here[^]
 
My basic question was: My objective is to allow the user to create an entire order without writing anything to the database. Once the user clicks save I want to write the whole thing at once. How do I accomplish that?
 
The relevant portion of the answer is:"...if you use entity objects to store data collected from users before submitting to the database you need to track entity state (new/modified/deleted etc) yourself across postbacks/page lifecycles and then use the L2S datacontext to read/write from the database."
 
So all the state tracking built into L2S is not only useless but cumbersome. L2S is not a repository. It does not afford me any mechanism for tracking entity state across layers of my application. It provides minimal support for (de)serialization, and I have to manage storage of objects in viewstate or session myself. That should be L2S's job! The code you wrote, while filling a certain need, does not appear to address this deficiency. Thus it becomes more icing on an inedible cake.
 
I'd love to be wrong about this because I'm knee deep in a very large app where these problems are killing my schedule!
 
Thanks for listening Smile | :)
GeneralRe: My vote of 1memberliammclennan19 Apr '09 - 17:09 
Good news! I think you are wrong Smile | :)
 
I agree that Linq-to-sql has some terrible flaws. And it seems now that they will never be fixed, however, your problem is with the web and the stateless nature of the HTTP protocol. Nearly everything in a web app happens in the lifetime of a HTTP request so there is no good way to persist state across requests. The problem you described exists in linq-to-sql, nhibernate, entity framework, ruby on rails and every other web development environment.
 
NHibernate has a solution called session-per-conversation which effectively uses the asp.net session to store the object context between requests. The other options are to use ajax to rewrite your application so that your entire order is created in a single HTTP request. Finally you can have a way to flag the various parts of the order as not being complete until the final save.
 
Don't store objects in session / viewstate. There are great performance problems with such an approach. Either keep everything on the same page, or persist to the database.
 
I like your cake metaphor.
 
Liam McLennan
liam@eclipsewebsolutions.com.au
www.eclipsewebsolutions.com.au

GeneralRe: My vote of 1memberswheat19 Apr '09 - 17:39 
>>.....so there is no good way to persist state across requests.
 
So then how am I wrong?
 
>>The other options are to use ajax to rewrite your application so that your entire order is created in a single HTTP request. Finally you can have a way to flag the various parts of the order as not being complete until the final save.
 
Yikes!! Do you have any idea how difficult that would be!! Plus there are concurrency issues.
 
I really need a repository that is in fact, a repository. And, as you said in your article, it should be reusable, domain driven, independent, and strongly typed. I should not have to write to temp tables or wrestle with session or write out temp files. Indeed, L2S would be just fine for me if it did what it's documentation says it does.
 
Just a few hours ago I stumbled on .net RIA which you can read about here.[^]
I'm not sure if is just another blind alley but it looks interesting.
GeneralRe: My vote of 1memberliammclennan19 Apr '09 - 17:48 
You are wrong because you are blaming Linq-to-sql for the nature of web applications.
 
What you need is to be a desktop / smart client / silverlight application.
 
What does the linq-to-sql documentation say that is not true?
 
Liam McLennan
liam@eclipsewebsolutions.com.au
www.eclipsewebsolutions.com.au

GeneralRe: My vote of 1memberswheat19 Apr '09 - 18:29 
I know I sound bitter but actually I'm not casting blame Smile | :) If I were to blame L2S, it would not be for the nature of web apps, but for a failure to work with the nature of web apps. I'm sure there are ways for L2S to work with a stateless protocol. You can probably think of one or two yourself right now. For example, wouldn't it be great to be able to call methods to serialize and write a datacontext to a row on a temp table. And another method to read and deserialize it. If something like that could be used with code like you've written in your article, the appearance to the other layers in the app would be one of a stateful repository.
For the record I am not proposing the preceeding as a single solution to the problems I've discussed. It is just an example I've pulled out of thin air to make the point that some kind of stateful repository is not an impossible objective. It may not be optimal or scalable, but it's not impossible. See this[^]

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130523.1 | Last Updated 5 Jun 2008
Article Copyright 2008 by liammclennan
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid