I want to do something that feels like it should be really simple, but has turned out to be a real headache. I’m Selecting from an IQueryable and instead of creating a new object, I want to “tweak” the one that I’m projecting. Here’s what I want to be able to do (context is a DbContext, and I’m using a code-first approach):
var eventsToReturn =
((IQueryable<Event>)
context.Events
.OrderByDescending(evnt => evnt.TakesPlaceOn)
.Skip(startAt)
.Take(numberToReturn)
)
.Select(evnt =>
{
evnt.UnmappedUsersCount = evnt.Users.Count();
return evnt;
}
)
.ToList();
This is nice because Entity Framework doesn’t have to actually get all the User entities associated with each event, and can use an aggregate COUNT function to simply get the number of users. However, it doesn’t work, because the IQueryable version of Select wants a lambda statement with no method body that can be converted into an expression tree. So, while the above doesn’t work, this does:
.Select(evnt => evnt)
… and so does this:
.Select(evnt => evnt.Users.Count())
I have solved the problem, but my way seems rather long-winded. Here’s my approach:
var eventsToReturnData =
((IQueryable<Event>)
context.Events
.OrderByDescending(evnt => evnt.TakesPlaceOn)
.Skip(startAt)
.Take(numberToReturn)
)
.Select(evnt =>
new {
Event = evnt,
UnmappedUsersCount = evnt.Users.Count()
}
)
.ToList();
var eventsToReturn = eventsToReturnData
.Select(evntData => {
evntData.Event.UnmappedUsersCount = evntData.UnmappedUsersCount;
return evntData.Event;
})
.ToList();
So I’m projecting the main Event object into an anonymous object, as well as the one tiny int that I want to add to it, and then doing a whole new Select on eventsToReturnData, which works this time because eventsToReturnData is an IEnumerable (so the lambda can have a method body), just to tweak the Event object by setting the UnmappedUsersCount integer that I got from the first query.
Is there a simpler or more elegant way to do this? I want to avoid EF having to retreive all the users for each event, so it’s no good just using Event.Users.Count() on what is returned by the IQueryable‘s Select method.
You could leverage the
Expressionclass to build a tree at runtime but that is so much work and code that it would be justified only if you had to do such a thing a lot of times.If this pattern occurs only once or rarely in your code this is the best you can do.