A while ago Rick Strahl raised a concern about returning dynamic query results in LINQ. It's a fairly common thing for people to return dynamic query results (i.e. create projections), using the new anonymous types feature. The consequence of using anonymous types is that you can't just return it from a method, because you don't know what to put for the method's return type. Or can you?
A trick you can use is to defer the projection to the caller. The data method would simply expect a 'projection' delegate/lambda, which it can use for the select part of the query. Because the non-projected query result type is always known, you can use this type as the parameter type of the 'projection' delegate/lambda. In code this looks like:
1
2
3
4
|
public IEnumerable<TProjection> GetCustomers<TProjection>(Func<Customer, TProjection> projection) {
return from c in _customers
select projection(c);
}
|
As you can see the return type is based on the type of the projection. The type of the projection is based on the type of projection that a caller uses. For example, in the code below the return type is an IEnumerable of the anonymous type defined in the call to GetCustomers.
1
2
|
var results = GetCustomers(customer => new { Name = customer.Name });
// results is of type IEnumerable<'anonymous type with property Name'>.
|
This trick also works with more complicated scenarios. For example, you can also deal with nested queries in a similar way.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public IEnumerable<TProjection> GetCustomersWithOrders<TProjection>(Func<Customer, IEnumerable<Order>, TProjection> projection) {
return from customer in _customers
let customerOrders = from order in _orders
where order.CustomerID = customer.ID
select projection(customer, customerOrders);
}
// ...
var results = GetCustomersWithOrders((customer, orders) => new {
Name = customer.Name,
OrderCount = orders.Count()
});
|
These examples use in-memory collections, but nothing prevents you from using the same technique with DLINQ or other APIs. When using DLINQ, you will just have to replace 'IEnumerable' in the above examples with 'IQueryable'.
But what about...
Now that said, this trick doesn't address the scenario where you have a complex projection, that you really wish to hide from the callers. For those scenarios I guess you don't really have a choice but to introduce a named type to hold the projection.