I'm writing a bit of LINQ to SQL that needs to perform a search for client information in our database, filtering the results and paging them. The filtering needs to be dynamic so I have broken the setup of the query into four stages:
- Find visible clients (basically apply a coarse grained filter)
- Filter clients by search criteria
- Sort and page the clients
- Retrieve additional client data
The code goes along the lines of:
// Step 1
var visibleClientQuery = from x in xs
from y in ys
from z in yas
where
...
select new
{
id = z.Client.Id,
a = z.Client.a,
b = z.Client.e.f.g.b
};
// Step 2
if (filterByA)
{
visibleClientQuery = visibleClientQuery.Where(client => client.a > 10);
}
else
{
visibleClientQuery = visibleClientQuery.Where(client => client.b.StartsWith("ABC"));
}
// Step 3
visibleClientQuery = visibleClientQuery.Distinct();
if (filterByA)
{
visibleClientQuery = visibleClientQuery.OrderBy(client => client.a);
}
else
{
visibleClientQuery = visibleClientQuery.OrderBy(client => client.b);
}
visibleClientQuery = visibleClientQuery.Skip(50).Take(30);
// Step 4
var fullQuery = from filterClientDetail in filteredClientQuery
join client in Clients on filterClientDetail.Id equals client.Id
...;
LINQ to SQL does a great job of combining these elements together to produce an efficient query. But one thing I want to do is reduce the number of joins created in the first, coarse query. If I'm filtering and sorting by A, there is no need for the first query to populate b using the line:
b = z.Client.e.f.g.b
Not populating b would save all that extra joining. I tried replacing the line with:
b = filterByA ? string.Empty : z.Client.e.f.g.b
Whilst this functionally works, when filterByA is true, the excess joins are still present in the query... slowing it down. My workaround is to introduce another interim query that wraps the Step 1 query:
// Step 1
var visibleClientQuery = from x in xs
from y in ys
from z in yas
where
...
select z.Client;
// Step 2
var filteredClientQuery = from client in visibleClientQuery
select new
{
id = client.Id,
a = client.a,
b = string.Empty
};
Then if we need to filter by B, this is replaced with:
filteredClientQuery = from client in visibleClientQuery
select new
{
id = client.Id,
a = 0,
b = client.e.f.g.b
};
So long as the replacement query returns an anonymous class with the same properties, this works, but seems like an unnecessary, heavyweight hack and doesn't allow easy mixing and matching of filters... what if we need to filter by A and B? The real query has several more possible filters.
Is there any way to programtically change how an individual property is populated within the anonymous class returned from a query... in much the same way that the where clause can be changed? Using filteredClientQuery.Select(...) I can swap out the entire select, but I can't see a way to work on an individual property.
Any help appreciated... even if it is to just confirm there is no solution!