I’ve solved my problem, but because I have tried and failed to solve it before, and this time it took me a good deal of effort, I wanted to post the question, and if nobody has a better answer, my solution so I don’t forget how to do this in the future, and to help anybody else facing a similar challenge. My challenge is this:
I have a function used to filter a list of options to return only those that are lot traced, and, when applicable, also only show items matching a specified pattern:
Private Shared Function FilterResultsLot(ByVal source As IQueryable(Of Item), _
ByVal itemCode As String) As IQueryable(Of Item)
If HasFilter(itemCode, True) Then
Return From row In source Where row.ItemDetail.IsLotTraced AndAlso _
row.ItemCode.ToLower() Like itemCode.ToLower()
Else
Return From row In source Where row.ItemDetail.IsLotTraced
End If
End Function
Another requirement came up and I wanted to be able to generalize the lot traced criteria into a general filter, so I created this function:
Private Shared Function FilterResults(source As IQueryable(Of Item), _
itemCode As String, filter As Func(Of Item, Boolean)) As IQueryable(Of Item)
Dim predicate As Func(Of Item, Boolean)
If HasFilter(itemCode, True) Then
predicate = Function(row) row.ItemCode.ToLower() Like itemCode.ToLower() AndAlso filter.Invoke(row)
Else
predicate = filter
End If
Return source.Where(predicate).AsQueryable()
End Function
And that function works through LINQ-to-SQL, but it loses significant optimization. Whereas the first function would an execute SQL query ending with:
FROM [dbo].[OITM] AS [t0]
LEFT OUTER JOIN [dbo].[FSE_ItemDetail] AS [t1] ON [t1].[ItemCode] = [t0].[ItemCode]
WHERE ([t1].[IsLotTraced] = 1) AND (LOWER([t0].[ItemCode]) LIKE @p0 ESCAPE ''~'')
The second would execute multiple SQL queries with these endings (and potentially many more):
FROM [dbo].[OITM] AS [t0]
FROM [dbo].[FSE_ItemDetail] AS [t0]
WHERE [t0].[ItemCode] = @p0',N'@p0 nvarchar(4000)',@p0=N'BF-BIKE'
FROM [dbo].[FSE_ItemDetail] AS [t0]
WHERE [t0].[ItemCode] = @p0',N'@p0 nvarchar(4000)',@p0=N'BF-BIKE-ITEM'
So the question is how to combine the LINQ-to-SQL delegate expressions into one without calling Invoke or AsQueryable, or without losing the optimal execution.
Here’s my current solution. Please comment or provide better solutions if you have any:
Edit:
After working with it a bit more, I prefer a simplification/alternative to this syntax:
My preference is:
(I also suggest ToUpper instead of ToLower to force case-insensitive match on case-sensitive server because I’ve heard UPPER is better optimized for that.)