17 Jan
2011

Entity Framework GetById<T>

I was asked the other day if I knew of a way to do GetById generically with Entity Framework. I thought that I had done something similiar in the past and the task would be easy, but it turned out to be a little bit more complicated.

After searching some forums and blogs, I came up with a result that I like a bit. First we will start with a simple test:

public class When_Getting_Product_By_Id : Rollback_Specification_Context<Container>

{

    [Test]

    public void Should_Return_Product()

    {

        Assert.IsNotNull(_product);

    }

 

    [Test]

    public void Should_Have_Proper_Id()

    {

        Assert.AreEqual(_productId, _product.ProductId);

    }

 

 

    private long _productId;

    private Product _product;

 

    protected override void Because()

    {

        _product = Repository.GetById<Product, long>(_productId);

    }

 

    protected override void Context()

    {

        base.Context();

        _productId = 1498;

    }

}

I will create  an IRepository, which will be the base interface for all of my entity repositories:

public interface IRepository

{

    T GetById<T, TId>(TId id);

}

Then an IProductRepository, which is empty for the moment, but would have other items in it in the future:

public interface IProductRepository : IRepository

{

}

I wanted to use Expressions to put together a dynamic lamda expression, so I have a method to get the property info. Some of this idea was stolen and changed from a forum post that I read. What it is ultimately doing is looking for the Primary Key column for the particular entity that is passed in and returning the info for that property:

public PropertyInfo GetKeyPropertyInfo(Type type)

{

    var properties = type.GetProperties();

    foreach (var propertyInfo in properties)

    {

        var attributes = propertyInfo.GetCustomAttributes(true);

        if (attributes.Any(attribute => attribute is EdmScalarPropertyAttribute && ((EdmScalarPropertyAttribute)attribute).EntityKeyProperty))

        {

            return propertyInfo;

        }

    }

    throw new ApplicationException(String.Format("No key property found for type {0}", type.Name));

}

Then I create the method for getting the id generically. At first, I just had GetById<T>(long id), but then I didn’t want someone to be held back by the type of id, in case they wanted to use a Guid or a long or anything else they desired:

public T GetById<T, TId>(TId id)

{

    var type = typeof (T);

 

    // x

    ParameterExpression argument = Expression.Parameter(type, "x");

    Expression expression = argument;

 

    // x.Id == id

    expression = Expression.Property(expression, GetKeyPropertyInfo(type));

    var valueExpression = Expression.Constant(id);

    expression = Expression.Equal(expression, valueExpression);

    

    // x => x.Id == id

    var lamda = Expression.Lambda<Func<T, bool>>(expression, argument);

 

    var container = this.MetadataWorkspace.GetEntityContainer(this.DefaultContainerName, DataSpace.CSpace);

    var set = (from meta in container.BaseEntitySets

                   where meta.ElementType.FullName == type.ToString()

                   select meta).First();

 

    return this.CreateQuery<T>("[" + set.Name + "]").FirstOrDefault(lamda);

}

I have tried to document what I am doing for those that are unfamiliar with Expressions. I am ultimately building up the lamda expression of “x => x.Id == id”, where the id is the value passed in and the x.Id is the property that has the key on it.

I then go through the BaseEntitySets on the container looking for the set that matches my entity. Careful on this part, your namespace for your container needs to match the project name that your models are in.

Once I find the set, I can then create the query and apply the lamda expression to find the product that I am looking for.

So the final code looks like this:

public partial class Container

{

    public T GetById<T, TId>(TId id)

    {

        var type = typeof (T);

 

        // x

        ParameterExpression argument = Expression.Parameter(type, "x");

        Expression expression = argument;

 

        // x.Id == id

        expression = Expression.Property(expression, GetKeyPropertyInfo(type));

        var valueExpression = Expression.Constant(id);

        expression = Expression.Equal(expression, valueExpression);

        

        // x => x.Id == id

        var lamda = Expression.Lambda<Func<T, bool>>(expression, argument);

 

        var container = this.MetadataWorkspace.GetEntityContainer(this.DefaultContainerName, DataSpace.CSpace);

        var set = (from meta in container.BaseEntitySets

                       where meta.ElementType.FullName == type.ToString()

                       select meta).First();

 

        return this.CreateQuery<T>("[" + set.Name + "]").FirstOrDefault(lamda);

    }

 

    public PropertyInfo GetKeyPropertyInfo(Type type)

    {

        var properties = type.GetProperties();

        foreach (var propertyInfo in properties)

        {

            var attributes = propertyInfo.GetCustomAttributes(true);

            if (attributes.Any(attribute => attribute is EdmScalarPropertyAttribute && ((EdmScalarPropertyAttribute)attribute).EntityKeyProperty))

            {

                return propertyInfo;

            }

        }

        throw new ApplicationException(String.Format("No key property found for type {0}", type.Name));

    }

}

And now my ProductRepository is a Partial of the Container and the ProductRepository implements the IProductRepository, so it has the GetById on it. The tests pass with flying colors of GREEN, so I am done.

5 thoughts on “Entity Framework GetById<T>

  1. Yes, this could easily be adapted. There are a few things that could be done. One thing is instead of passing an id through, you could actually pass a List of selectors using a Func type syntax. Like: List> selectors and then instead of getting the propertyInfo and building up the Lamda Expression, you would already have them and could add those to the FirstOrDefault() or Where clause.

    Another way would be passing some sort of dictionary through with a property name and value and then modifying the GetKeyPropertyInfo to return a set of properties, and then look through that set of the matching property.

    These are the first things that jump to mind. My preferred way would be the first one that I said.

    This definitely does raise a question though, using composite keys on an ORM or EDM is a philosophical point, but I usually take the approach that all my tables have their own unique key and not a composite key. Again, this is a philisophical question.

  2. Using this little helper from the Rob Conery’s MVC Starter Kit:

    private readonly ObjectContext _context;

    private string GetSetName()
    {
    //If you get an error here it’s because your namespace
    //for your EDM doesn’t match the partial model class
    //to change – open the properties for the EDM FILE and change “Custom Tool Namespace”
    //Not – this IS NOT the Namespace setting in the EDM designer – that’s for something
    //else entirely. This is for the EDMX file itself (r-click, properties)

    PropertyInfo entitySetProperty =
    _context.GetType().GetProperties()
    .Single(p => p.PropertyType.IsGenericType && typeof (IQueryable)
    .MakeGenericType(typeof (T)).IsAssignableFrom(p.PropertyType));

    return entitySetProperty.Name;
    }

    And supporting only models with Guid Id {get;set;} keys you can do following:

    public T SingleOrDefault(Guid id)
    {
    string qualifiedEntitySetName = _context.DefaultContainerName “.” GetSetName();

    object item;
    bool found = _context.TryGetObjectByKey(new EntityKey(qualifiedEntitySetName, “Id”, id), out item);
    return (T) (found ? item : null);
    }

    public T Single(Guid id)
    {
    string qualifiedEntitySetName = _context.DefaultContainerName “.” GetSetName();

    return (T) _context.GetObjectByKey(new EntityKey(qualifiedEntitySetName, “Id”, id));
    }

  3. I did start with doing something very similiar to the above comment. I wanted to use lamda expressions instead of doing the GetObjectByKey bit.

    I think the real question here is why do we have to do all of this work in the first place? It would seem that the key should be stored somewhere and it should be built into EF to know which field represents the id and how to get to it if we are talking about a certain Entity.

    Just food for thought.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.