Home

PROBLEM with LINQKit pluggable expressions on OData V3 Query in LINQPad v5.22.02

edited September 2017
When trying to run the following VB statements in LINQPad v5.22.02 (I've not tested this on other older versions of LINQPad though), I got a NotSupportedException with the message "The method 'Contains' is not supported". So obviously, OData V3 doesn't support the usage of the Contains operator in such a manner:

Dim SpecificIDsToFetch As Integer() = {1, 2, 3} Dim QueryFromOdataV3Source = From r In refProposalStatusSet _ Where SpecificIDsToFetch.Contains(r.proposalStatusID) _ Select r QueryFromOdataV3Source.Dump

So, I turned to LINQKit as a solution and came out with the following statements instead:

Dim SpecificIDsToFetch = Enumerable.Range(1,10).ToArray Dim predicate = SpecificIDsToFetch.Aggregate(PredicateBuilder.[New](Of refProposalStatus), Function(es, SpecificID) es.Or(Function(s) s.proposalStatusID = SpecificID) ) Dim QueryFromOdataV3Source = refProposalStatusSet.AsExpandable.Where(predicate.Compile) QueryFromOdataV3Source.Dump

Although the above statements work (the outcome is as expected), I'm disappointed that the filtering is done on the client (LINQPad itself) not on the server since according to LINQPad's Request Log the query above translates into the following request:

https://MyServer/MyApp/MyOdataV3.svc/refProposalStatusSet

I was actually expecting that the generated request would resemble the following instead:

https://MyServer/MyApp/MyOdataV3.svc/refProposalStatusSet()?$filter=proposalStatusID eq 1 or proposalStatusID eq 2 or proposalStatusID eq 3

Did I do something wrong or Is this a bug in LINQKit, LINQPad's built-in WCF Data Services 5.5 (OData 3) built-in driver or LINQPad itself?

If the source EntitySet contains just a handful of records, I don't mind if LINQPad downloads the whole set onto the client's memory and filters it locally but it's simply not feasible for large sets. I plan to also fetch related entities within the same query so the amount of data to be downloaded will be huge. That's why server-side filtering is very important for me.

I tried both LinqKit for EntityFramework (with IAsync support) v1.1.9.0 and LinqKit.Core v1.1.9.0. Both could not produce the expected Odata request (with server-side filtering).

I hope somebody (especially @JoeAlbahari , since you are involved in the creation of both LINQPad and LINQKit) could offer a solution to my problem.


Comments

  • LINQKit includes an Expand() method to which simplifies predicate chains.

    Try calling:

    refProposalStatusSet.Where(predicate.Expand())
  • edited September 2017
    Thanks for responding Joe..

    Unfortunately when I tried your suggestion, I got the following error instead:

    BC30519 Overload resolution failed because no accessible 'Where' can be called without a narrowing conversion:
    Extension method 'Public Function Where(predicate As Expression(Of Func(Of refProposalStatus, Boolean))) As IQueryable(Of refProposalStatus)' defined in 'Queryable': Argument matching parameter 'predicate' narrows from 'Expression' to 'Expression(Of Func(Of refProposalStatus, Boolean))'.
    Extension method 'Public Function Where(predicate As Expression(Of Func(Of refProposalStatus, Integer, Boolean))) As IQueryable(Of refProposalStatus)' defined in 'Queryable': Argument matching parameter 'predicate' narrows from 'Expression' to 'Expression(Of Func(Of refProposalStatus, Integer, Boolean))'.


    Also, an additional question: What 'version' of LINQKit should I actually use for querying OData services? LINQKit.Core or "LinqKit for EntityFramework (with IAsync support)"?
  • I tested it with the plain version of LINQKit:
    http://www.albahari.com/nutshell/linqkit.aspx

    If the overload resolution still doesn't work, does it make any difference if you try it in C#?
  • edited October 2017
    The overload resolution still doesn't work with the plain LINQKit from that website.

    It's really slow for me to rethink almost everything in C# (being a VB.NET developer for over a decade now).

    Besides, I'm building this query for my non-programmer users and I've just finished teaching them how to modify/customize my base queries (all of which are in VB) themselves. I think VB is the easiest for them to understand since, among other things, it features more English words than C# (imho), case-insensitivity when using reserved keywords and there's no need to end statements with a delimiter (";" in case of C#).

    Anyway, I managed to "translate" the simple query above into C# by hand and as you've said, it works (the expected OData query is produced) except that I have to use PredicateBuilder.False<refProposalStatus>() instead since PredicateBuilder.New<T>() doesn't exist in the plain LINQKit.

    HOWEVER, when I swapped out the plain LINQKit for LINQKit.Core or the "LinqKit for EntityFramework (with IAsync support)" and use PredicateBuilder.New<T>() instead, a similar error message is flagged at the predicate.Expand() line :

    CS1503 Argument 2: cannot convert from 'System.Linq.Expressions.Expression' to 'System.Linq.Expressions.Expression<System.Func<LINQPad.User.refProposalStatus, bool>>'

    So, somehow the Expand() function is incompatible with PredicateBuilder.New() , right?

    When I replaced PredicateBuilder.New<T>() with PredicateBuilder.False<T>() , the code runs despite the warning below:

    CS0618 'PredicateBuilder.False<t>()' is obsolete: 'Use PredicateBuilder.New() instead'

    When I switched back to the VB version of the query, it works as expected (i got the server-side filtering OData query) when i changed PredicateBuilder.[New](Of refProposalStatus) to PredicateBuilder.False(Of refProposalStatus) (using all 3 versions of LINQKit). This is of course accompanied by the obsolete warning message when using the non-plain versions.

    So, I guess this is not a language-specific thing. A bug somewhere else perhaps?
  • OK, just to let you know that I found a temporary solution to this problem.

    When using PredicateBuilder.New<T>() , the result of the Expand() function needs to be explicitly casted to the type Expression<Func<T, bool>> in order for the CS1503 error message to go away and allowing the query to be compiled and executed.

    So, instead of a clean :

    refProposalStatusSet.Where(predicate.Expand())

    I have to do the following instead:

    refProposalStatusSet.Where(DirectCast(predicate.Expand(), Expression(Of Func(Of refProposalStatus, Boolean))))

    A bit ugly in my opinion but it gets the code running and achieves the expected server-side query.

    I still hope that this return type mismatch will be fixed later in LINQKit or whichever components that caused it.
Sign In or Register to comment.