1

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.

net_prog
  • 9,921
  • 16
  • 55
  • 70

2 Answers2

1

Use Automapper for this. With Automapper you can do:

Mapper.CreateMap<Product, ProjectedProduct>
    .ForMember(dto => dto.ProdID, m => m.MapFrom(p => p.ID))
    .ForMember(dto => dto.ProdTitle , m => m.MapFrom(p => p.Title));

(ForMember specifies a mapping between properties with different names, Automapper automatically maps properties with identical names).

Now you defined a reusable projection.

Later on you can do:

var projected = Context.Products.Project().To<ProjectedProduct>().ToArray();

Automapper is widely used and you will find many examples of how to use it.

Gert Arnold
  • 105,341
  • 31
  • 202
  • 291
0

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

Gary.S
  • 7,076
  • 1
  • 26
  • 36
  • Thanks, this is what I was looking for. I thought it should have been something with Expression, but didn't manage to solve myself. – net_prog Apr 03 '13 at 00:01