Thursday, April 03, 2008

Repository.GetById using LINQ Expression Syntax

A while ago I talked about using the IRepository<T> pattern with LINQ to SQL. One of the methods of my repository is GetById. The slight difficulty here is that we need to discover the primary key property of the (generated) type at runtime so we can't use a vanilla lambda expression in the Where clause. Before I used the DynamicQueriable helper library, but I've just re-written the function using an explicit expression tree which removes the need to reference the helper library.

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

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

    return dataContext.GetTable<T>().Where(whereExpression).Single();
}

Here is the extension method that finds the primary key:

public static PropertyInfo GetPrimaryKey(this Type entityType)
{
    foreach (PropertyInfo property in entityType.GetProperties())
    {
        ColumnAttribute[] attributes = (ColumnAttribute[])property.GetCustomAttributes(typeof(ColumnAttribute), true);
        if (attributes.Length == 1)
        {
            ColumnAttribute columnAttribute = attributes[0];
            if (columnAttribute.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));
}

7 comments:

Anil Kumar T.R said...

Repository pattern discussed here is great.. Suppose if we need to cache business objects... where do we handle caching? DAL or BIZ layer?

Thanks

Mike Hadlow said...

Hi Anil,

Thanks for the comment. In my opinion caching should be the responsibility of the DAL. It's an infrastructure thing, and your Domain or Business Layer should simply be responsible for representing your domain rules.

LINQ-to-SQL does object caching for the lifetime of the DataContext, but then you've got to decide how long you want your DataContext hanging around for. In most web applications it would be a mistake have a lifetime beyond the request, which is probably not what you want.

Anonymous said...

Really helped me a lot and works like a charm. Thanks

Joji Thomas said...

Thank you, This was really useful to me......

Andreas Grech said...

Mike,

Why are you throwing an Exception if the Primary Key is not of type int? What if you have a PK set as GUID (string) ?

Mike Hadlow said...

Hi Dreas,

In my case all my primary keys are ints. If you want to use GUIDs, just change the code to check for them instead.

Bryan Avery said...

Very nice, just came across the same issue, you saved me a lot of time