Search code examples
c#entity-framework-5projection

Shared projection?


I have a database and an Entity Framework model mapped to it. The database has a table "Products" with numerous columns. In many EF queries I need only a few columns, and I do a projection, let's say

var projected = Context.Products
    .Select(p => new ProjectedProduct { ProdID = p.ID, ProdTitle = p.Title })
    .ToArray();

Since this projection is used many times, I move it to a separate method:

public static IQueryable<ProjectedProduct> ToProjectedProduct(this IQueryable<Product> query)
{
    return query.Select(p => 
        new ProjectedProduct { ProdID = p.ID, ProdTitle = p.Title });
}

So I can use the projection like:

var projected = Context.Products.ToProjectedProduct().ToArray();

Now I also want to use the same projection for a single instance of product, like:

var prod = Context.Products.First(p => p);
var projected = new ProjectedProduct { ProdID = prod.ID, ProdTitle = prod.Title });

And I still want to use the same helper method for the projection in order to have it in one place, but it won't work, because it works only for IQueryable. What I can do is convert the projection to another method like

public static ProjectedProduct ToProjectedProduct(this Product p)
{
    return new ProjectedProduct { ProdID = p.ID, ProdTitle = p.Title });
}

But now this method won't work for IQueryable. I need a helper method which would work for both cases, what I would want to do is:

var projected = Context.Products.Select(p => p.ToProjectedProduct()).ToArray();

But this doesn't work because the helper method can't be translated to the database query.


Solution

  • The select extension method of an IQueryable requires an Expression<Func<TSource, TResult>>. What this means is we can define an object elsewhere that matches this parameter and pass it into our select statement.

    Property (could also be a method):

    public Expression<Func<Product, ProjectedProduct>> MapProduct
    {
        get
        {
            return p => new ProjectedProduct { ProdID = p.ID, ProdTitle = p.Title };
        }
    }
    

    IQueryable usage:

    var projected = Context.Products
        .Select(MapProduct)
        .ToArray();
    

    Single instance usage:

    var projected = Context.Products.Select(MapProduct).First();
    

    IEnumerable usage:

    // calling compile turns the expression into a normal Func
    var projected = ProductList.Select(MapProduct.Compile());
    

    Normal Object usage:

    var projected = MapProduct.Compile()(ProdObj);
    

    MSDN IQueryable.Select - http://msdn.microsoft.com/en-us/library/bb534638(v=vs.100).aspx