I have been using a pattern for projecting from Entity Framework to business domain views. I am nesting it, i.e. calling one projection from within another projection. It works well for collections, but I can't work out how to use the same pattern for projecting a single entity.
For collections, my code looks something like:
public class PersonView
{
public int Id {get;private set;}
public string FullName { get; set; }
public static Expression<Func<Person, PersonView>> Projector = p => new PersonView {
Id = p.PersonId,
FullName = p.FirstName + " " + p.LastName
};
}
//...
context.People.Select(PersonView.Projector).ToList(); // returns a list of PersonViews
If I create a list containing the 1 element, or otherwise get creative with the LINQ, I can get it to work, but would prefer a neater solution if possible.
// convert single element to list, then project it. Works, but is messy
var orderDetails = context.Orders.Where(...)
.Select(o => new {
Id = o.Id,
Date = o.Date,
PersonView = new [] { o.Person }.AsQueryable().Select(PersonView.Projector).FirstOrDefault()
}).FirstOrDefault();
I would like something like (the below does not work, because linq to entities cannot invoke the Func<>):
public class PersonView
{
public int Id {get;private set;}
public string FullName { get; set; }
public static Func<Person, PersonView> ProjectorFn = p => new PersonView {
Id = p.PersonId,
FullName = p.FirstName + " " + p.LastName
};
public static Expression<Func<Person, PersonView>> ProjectorExpr = p => ProjectorFn(p);
}
var orderDetails = context.Orders.Where(...)
.Select(o => new {
Id = o.Id,
Date = o.Date,
PersonView = PersonView.ProjectorFn(o.Person)
}).FirstOrDefault();
//...
var peopleWithOrders = context.People.Where(p => p.Orders.Any())
.Select(PersonView.ProjectorExpr);
Any suggestions?
The essence of the problem is that the following line in your projection
Cannot be translated into a store query because
ProjectorFn
is no longer anExpression
but a generic delegate (Func<Person, PersonView>
).Now, what you actually want is to use the original expression contained in your
PersonView.Projector
field, but obviously you can't because it cannot be invoked (without compiling to delegate) hence cannot return your desiredPersonView
type.LinqKit is aiming to solve this problem using its own
Invoke()
extension method that while letting your code compile, will make sure your expression gets replaced back to its original form.To enable the interception, you have to use the
AsExpandable()
method that is extending the entity set:More on LinqKit