Question

I have C# extension methods on IQueryable, e.g. FindNewCustomers() and FindCustomersRegisteredAfter(int year) and so forth which I use to "chain" a query together for LINQ to SQL.

Now to my problem: I want to create compiled queries, e.g.:

 private static Func<MyDataContext, SearchInfo, IQueryable<Customer>>
        CQFindAll = 
            CompiledQuery.Compile((MyDataContext dc, SearchInfo info) =>
                dc.Contacts.Select(c => c).FindCustomersRegisteredAfter(info.RegYear)
                           .OrderBy(info.OrderInfo)
                           .Skip(info.SkipCount)
                           .Take(info.PageSize));

The FindCustomersRegisteredAfter(int year) method is an extension method taking a IQueryable and returning the same. The OrderBy method is an extension method as well (System.Linq.Dynamic) which creates a dynamic expression based on a string (e.g. "FirstName ASC" will sort the field FirstName ascending). Skip and Take are the built-in methods.

The above (not as compiled query, but regular query) works perfect. Once I put it in a compiled query, I hit the following error:

Method 'System.Linq.IQueryable`1[Domain.Customer] FindCustomersRegisteredAfter[Customer](System.Linq.IQueryable`1[Domain.Customer], Int32)' has no supported translation to SQL.

Once again, this works perfectly if the query is non-compiled, just a regular LINQ query. The error appears only once it is inside of CompiledQuery.Compile().

Help??!

Edit: If I create the query via var query = (...) the same way as inside of CompiledQuery.Compile, this is the SQL generated:

SELECT [t1].[Id], [t1].[FirstName], [t1].[LastName], 
       [t1].[RegYear], [t1].[DeletedOn]
FROM (
 SELECT ROW_NUMBER() OVER (ORDER BY [t0].[LastName]) AS [ROW_NUMBER], 
        [t0].[Id], [t0].[FirstName], [t0].[LastName], [t0].[RegYear], 
        [t0].[DeletedOn]
FROM [dbo].[Contacts] AS [t0]
WHERE ([t0].[RegYear] > @p0) AND ([t0].[DeletedOn] IS NULL)
     ) AS [t1]
WHERE [t1].[ROW_NUMBER] BETWEEN @p1 + 1 AND @p1 + @p2
ORDER BY [t1].[ROW_NUMBER]

So you see that the SQL is all perfectly translatable so that I only need to fill in @p0, @p1 and @p2 for this to work repeatedly! What is wrong with CompiledQuery.Compile?!?

Update: I understand that OrderBy can't work (as it's not a @p parameter). I'm still trying to figure out why CompiledQuery.Compile won't work with my extension methods though. Information on the internet to this topic is virtually non-existent.

Was it helpful?

Solution

I believe the compiled query must be translatable to SQL, which your extension method cannot be. If you profile the SQL created by your "regular" query you may find it is selecting the entire table so it can feed all the rows into your extension method.

You'd do better to put your filtering logic in the query (as part of the expression tree) so it can be translated to SQL and run server side.

The OrderBy is also a problem because of the Skip. You need to make this translatable to SQL or LINQ will have to return all the rows in order to filter them down client side.

If you can't express these as LINQ expressions, consider creating SQL Functions on the server and mapping them to your DataContext. LINQ will be able to translate those to the T-SQL function calls.

EDIT:

I guess I was assuming your extension methods weren't building expression trees. Sorry.

Consider this link which seems similiar to your problem. It references another link that goes into more detail.

It looks like the MethodCallExpression is the issue.

The code is a bit long to be posted here, but similar to tomasp.net's expander, I visit every expression in the expression tree and if the node is a MethodCallExpression which calls a method that returns an expression tree, I replace that MethodCallExpression by the expression tree returned by invoking the method.

So, it appears the problem is that when compiling the query, the method is not executed so there is no expression tree to translate to SQL.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top