Skip to content

LINQ to SQL Generic Repository

After my last post regarding the strange NotSupportedException I was receiving on my generic repository, I thought it might be fitting to post what my LINQ to SQL generic repository looks like and what it’s all about.

WHAT’S A REPOSITORY?

A repository defines methods which allow you to access data from your data model, typically from a database.  You would be implementing the “Repository Pattern” when working with repositories, Martin Fowler describes the principal here:

A Repository mediates between the domain and data mapping layers, acting like an in-memory domain object collection. Client objects construct query specifications declaratively and submit them to Repository for satisfaction. Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes. Conceptually, a Repository encapsulates the set of objects persisted in a data store and the operations performed over them, providing a more object-oriented view of the persistence layer. Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers.

This is what a typical repository interface could look like:

public interface IProductRepository
{
    Product GetById(int id);
    void Add(Product product);
    void Remove(Product product);
}

WHY A GENERIC REPOSITORY?

I don’t have a lot of time to post this, so I will keep it short.  There is a ton of information if you do a search on generic repositories such as this and this here, but I will say, once you start using it you will soon realise the benefits are phenomenal.

For instance some of the biggest dividends to be gained is that there is only one repository specification.  This results in: -

  1. A single entry point to your repository for your entire domain model.
  2. A lot less code means a lot less refactoring. Due to the fact by design it is typically one (1) interface and one (1) class means a lot less code, and a lot less refactoring if changes ever had to be made to your repository.  In a non-generic scenario, you would be changing your code in each class and respective interface for each model or entity in your database, you could see how this could quickly get quite unwieldy.

Now I will state frankly the below generic repository was derived from the one featured in “Suteki Shop“, a brilliant open-source ASP.NET MVC eCommerce application.  After looking for a template to suit, I came across this one, it had some great features and as you’ll see in the sample app attached I have made a few extensions on it, including another interface implementation to facilitate the ability to change some generic data pre-save for Insert and Delete and a DeleteAllOnSubmit() method.  However, to keep things simple and so you can get up and running straight away the code in the snippets below does not include the extended interface, only the sample app does.  Below is just a bare bones generic repository implementation you can get using straight away.

So check it out, I explain underneath how it can be called, as I understand “Suteki Shop” can be quite daunting for first time users, and we don’t all have the time to look through a large code-base.

SHOW ME THE CODE

Firstly the Interface, which will define the contract our repository will follow:

    public interface IRepository where T : class
    {
        T GetById(int id);
        IQueryable GetAll();
        void InsertOnSubmit(T entity);
        void DeleteOnSubmit(T entity);
        void DeleteAllOnSubmit(IEnumerable entities);
        void SubmitChanges();
    }

Now the repository class itself:

    public class Repository : IRepository where T : class, IDbTable
    {
        readonly AdventureWorksDataContext dataContext;

        public Repository()
        {
            dataContext = new AdventureWorksDataContext();
        }

        public virtual T GetById(int id)
        {
            var itemParameter = Expression.Parameter(typeof(T), "item");

            var whereExpression = Expression.Lambda>
                (
                Expression.Equal(
                    Expression.Property(
                        itemParameter,
                        typeof(T).GetPrimaryKey().Name
                        ),
                    Expression.Constant(id)
                    ),
                new[] { itemParameter }
                );

            var item = GetAll().Where(whereExpression).SingleOrDefault();

            if (item == null)
            {
                throw new PrimaryKeyNotFoundException(string.Format("No {0} with primary key {1} found",
                                                                    typeof(T).FullName, id));
            }

            return item;
        }

        public virtual IQueryable GetAll()
        {
            return dataContext.GetTable();
        }

        public virtual void InsertOnSubmit(T entity)
        {
            GetTable().InsertOnSubmit(entity);
        }

        public virtual void DeleteOnSubmit(T entity)
        {
            DeleteOnSubmit(entity, true);
        }

        public virtual void DeleteAllOnSubmit(IEnumerable entities)
        {
            DeleteAllOnSubmit(entities, true);
        }

        public virtual void SubmitChanges()
        {
            dataContext.SubmitChanges();
        }

        public virtual ITable GetTable()
        {
            return dataContext.GetTable();
        }

    }

Now for additional helper classes/interfaces:

    public static class Extras
    {
        public static PropertyInfo GetPrimaryKey(this Type entityType)
        {
            foreach (PropertyInfo property in entityType.GetProperties())
            {
                if (property.IsPrimaryKey())
                {
                    if (property.PropertyType != typeof(int))
                    {
                        throw new ApplicationException(string.Format("Primary key, '{0}', of type '{1}' is not int",
                                                                     property.Name, entityType));
                    }
                    return property;
                }
            }

            throw new ApplicationException(string.Format("No primary key defined for type {0}", entityType.Name));
        }

        public static bool IsPrimaryKey(this PropertyInfo propertyInfo)
        {
            var columnAttribute = propertyInfo.GetAttributeOf
();
            if (columnAttribute == null) return false;
            return columnAttribute.IsPrimaryKey;
        }

        public static TAttribute GetAttributeOf(this PropertyInfo propertyInfo)
        {
            object[] attributes = propertyInfo.GetCustomAttributes(typeof(TAttribute), true);
            if (attributes.Length == 0)
            {
                return default(TAttribute);
            }
            return (TAttribute)attributes[0];
        }

    }

    [Serializable]
    public class PrimaryKeyNotFoundException : Exception
    {
        //
        // For guidelines regarding the creation of new exception types, see
        //    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/cpgenref/html/cpconerrorraisinghandlingguidelines.asp
        // and
        //    http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dncscol/html/csharp07192001.asp
        //

        public PrimaryKeyNotFoundException()
        {
        }

        public PrimaryKeyNotFoundException(string message)
            : base(message)
        {
        }

        public PrimaryKeyNotFoundException(string message, Exception inner)
            : base(message, inner)
        {
        }

        protected PrimaryKeyNotFoundException(
            SerializationInfo info,
            StreamingContext context)
            : base(info, context)
        {
        }
    }

Now below is a few examples of calling on the data using the “Adventure Works” database in a console application:

namespace GenericRepoSample
{
    class Program
    {
        private static IRepository productRepository;
        private static IRepository productModelRepository;

        static void Main(string[] args)
        {
            productRepository = new Repository();
            productModelRepository = new Repository();

            Console.WriteLine("Printing top 10 products..." + Environment.NewLine + "=====================================================");
            foreach (var s in productRepository.GetAll().Take(10))
                Console.WriteLine(s.Name);

            Console.WriteLine();
            Console.WriteLine("Printing top 10 product models..." + Environment.NewLine + "=====================================================");
            foreach (var s in productModelRepository.GetAll().Take(10))
                Console.WriteLine(s.Name);

            Console.Write("Press any key.");
            Console.ReadLine();
        }
    }
}

So if you are not quite sure how things are running, step through the code in debug, or if it’s generics you’re stuck on go read up on .NET generics.

Below is a sample you can use, and demonstrates the IDbTable interface.  The IDbTable interface as I explained facilitates the ability to perform some data manipulation on pre-save which is common across the entire data model.  You will note each LINQ to SQL entity must implement this interface for this function to work, this means creating a partial class for each entity in your database and obviously not practical to upkeep manually, so attached is a T4 template I created to do the job each time you update your LINQ to SQL classes.  All that is required is including the .tt template in your project, altering the namespace and database connection details and then right clicking -> “Run Custom Tool”.

Remember, this only works if you have common columns shared across all your database tables, and for any exceptions to the rule you will see I have a conditional statement you can alter to fit your needs and also only required if you have data you wish to change on each command, such as how I update the modification date on each delete. Adventure Works 2008 database available here.  In the real world, if you would

Download: Generic Repository Sample Application

Download: T4 .TT Template for IDbTable Support

Download: AdventureWorks 2008 Sample Database from Microsoft

The sample above attached has been altered to just simple initiate a new Data Context in the repository each time, if you want the original example with the included dependency injection container, check the full sample code over at Suteki Shop.

VN:F [1.9.1_1087]
Rating: 4.6/5 (9 votes cast)
VN:F [1.9.1_1087]
Rating: +3 (from 3 votes)
LINQ to SQL Generic Repository, 4.6 out of 5 based on 9 ratings
Bookmark and Share
kick it on DotNetKicks.com
Shout it

NOW, FOR A WORD FROM OUR SPONSORS

7 Comments

  1. Joseph Ferris

    Nice write up. I have implemented something similar in our application architecture. The only real difference is that I have opted to move away from the IQueryable GetAll() approach for more fine-grained FindBy…() calls. Of course, this seems to represent two sides of a currently raging debate that we don’t need to reproduce here. ;-)

    VA:F [1.9.1_1087]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.1_1087]
    Rating: 0 (from 0 votes)
    Posted on 31-Jul-09 at 6:16 am | Permalink
  2. Graham O'Neale

    Very true :)
    Yes I don’t mind a FindBy() call however as you can see I don’t mind traversing my IQueryable, so I am happy to use GetAll().Where(…) which let’s me use an out of the box filtering solution whenever I need it – LINQ. But for anything I might do more than once, I usually wrap this up into a business service, and that would call the LINQ query and return the enumerated result.

    VN:F [1.9.1_1087]
    Rating: 0.0/5 (0 votes cast)
    VN:F [1.9.1_1087]
    Rating: 0 (from 0 votes)
    Posted on 03-Aug-09 at 2:46 am | Permalink
  3. Your post was recommended by someone on stacked overflow as an answer to a question. Let me just say one thing. This is fabulous!!! It’s what I have been seeking for quite a while. Thank you so much for sharing it. Wow!

    VA:F [1.9.1_1087]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.1_1087]
    Rating: 0 (from 0 votes)
    Posted on 11-Sep-09 at 9:40 pm | Permalink
  4. Graham O'Neale

    @Anthony: No problem dude, glad to help :)

    VN:F [1.9.1_1087]
    Rating: 0.0/5 (0 votes cast)
    VN:F [1.9.1_1087]
    Rating: 0 (from 0 votes)
    Posted on 12-Sep-09 at 3:45 am | Permalink
  5. Daniel Cotter

    I believe you’re missing a after IRepository in the first line:

    public interface IRepository where T : class

    It’s there in your sample code, but not on your web page.

    VA:F [1.9.1_1087]
    Rating: 3.0/5 (2 votes cast)
    VA:F [1.9.1_1087]
    Rating: 0 (from 2 votes)
    Posted on 18-Sep-09 at 1:09 pm | Permalink
  6. IRepository is kinda mandatory when building assemblies/ layers, but your implementation into a common Repository is imho strange – you have to take some things for granted:
    – PK will never be composite
    – All entities have the same deletion and update constraints.

    Just my 2 cent :cool: , but of course… if that is the intention, it’s a nice implementation.

    VA:F [1.9.1_1087]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.1_1087]
    Rating: 0 (from 0 votes)
    Posted on 27-Nov-09 at 8:00 am | Permalink
  7. Very true

    VA:F [1.9.1_1087]
    Rating: 0.0/5 (0 votes cast)
    VA:F [1.9.1_1087]
    Rating: 0 (from 0 votes)
    Posted on 09-Jun-10 at 1:57 pm | Permalink

One Trackback/Pingback

  1. [...] implementing an IRepository. If you still want more (you devil you), you can peruse Graham’s article on the generic repository model. All three articles serve as inspiration for the creation of my Repository [...]

Post a Comment

Your email is never published nor shared. Required fields are marked *
*
*
My name is Graham O'Neale and I'm a software architect from Gold Coast, Australia. I am an overtime thinker, full time coder and awake part time in the real world. I have a keen interest in software development, particularly in the realm of programming (C#, ASP.NET, ASP.NET MVC, LINQ (2 SQL), Entity Framework, Silverlight, Blend, WCF, WPF) and a keen interest in the cutting edge and innovation. I have a new found love for design patterns, ALT.NET practices and well crafted software architecture. The purpose of this blog is to express any thoughts, findings, tips and gripes along my travels in the wonderful world of coding and technology...