zzzprojects / System.Linq.Dynamic.Core

The .NET Standard / .NET Core version from the System Linq Dynamic functionality.
https://dynamic-linq.net/
Apache License 2.0
1.55k stars 228 forks source link

Argument exception when filtering with any(Member.Function.Contains) #242

Closed ghost closed 4 years ago

ghost commented 5 years ago

Hey, we ran into an error that we couldn’t explain ourselves. We have a User class that contains the method “string GetDisplayName(bool, bool, bool)”. The following query works when querying the Owner, which is of type User, on the Project dataset:

// Get the projects, where the owners display name contains “John”.
Owner.GetDisplayName(true, true, false).Contains("John")

The above query runs as expected. This query below doesn’t however. Here we have an instance of that class shared with a user, where the connection is made through a UserShare object. The query is run on Projects:

// Get all projects that are shared with anyone where the users display name contains “John”.
UserShares.Any(User.GetDisplayName(true,true,false).Contains("John"))

The resulting error says :
(tell me, if you need the stack trace)

System.ArgumentException: Method 'System.String GetDisplayName(Boolean, Boolean, Boolean)' 
declared on type 'SWSF.Model.UserService.BaseUser' cannot be called with instance of type 
'System.String'

We use EF Core 2.2.1 and access a PostgreSQL database, if that is of any use.

Any help would be appreciated. It seems like a bug to us, but we could be wrong about that. Thanks in advance and have a nice day :-)

StefH commented 5 years ago

When using Dynamic Linq, I would expect code like:

var result = context.UserShares.Select("it.GetDisplayName(true, true, true).Contains(\"John\")");

See https://github.com/StefH/System.Linq.Dynamic.Core/blob/null/src-console/ConsoleAppEF2.0.2_InMemory/Program.cs#L86

ghost commented 5 years ago

Hey Stef, thanks for the quick answer! I think there is some missing information on our part, we try to execute the query something like this:

var projects = context.Projects;
var filter = "UserShares.Any(User.GetDisplayName(true,true,false).Contains(\"John\"))";
var filtered = projects.Where(filter);
StefH commented 5 years ago

This code should work:

var projects = new[]
{
  new { UserShares = new [] { new User { Name  = "John" } } }
}.AsQueryable();

var filter = "UserShares.Any(GetDisplayName(true,true,false).Contains(\"John\"))";
var filtered = projects.Where(filter);

See same file for example.

ghost commented 5 years ago

Hey, we ran some tests and could only reproduce this error when using a real database, so not an in-memory one. Using the in-memory db, all the queries we tested completed successfully. We are using the Npgsql.EntityFrameworkCore.PostgreSQL provider.

Looking at the stack trace, we guess that maybe the function is found correctly with the (bool, bool, bool) signature using reflection and then the parameters are inserted as strings, instead of bool, so the error occurrs. But that's just a wild guess.

System.ArgumentException Method 'System.String GetDisplayName(Boolean, Boolean, Boolean)' declared on type SWSF.Model.UserService.BaseUser' cannot be called with instance of type 'System.String'
   at System.Linq.Expressions.Expression.ValidateCallInstanceType(Type instanceType, MethodInfo method)
   at System.Linq.Expressions.Expression.ValidateStaticOrInstanceMethod(Expression instance, MethodInfo method)
   at System.Linq.Expressions.Expression.ValidateMethodAndGetParameters(Expression instance, MethodInfo method)
   at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, Expression arg0, Expression arg1, Expression arg2)
   at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable'1 arguments)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.SqlTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.SqlTranslatingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.SqlTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.SqlTranslatingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.VisitWhereClause(WhereClause whereClause, QueryModel queryModel, Int32 index)
   at Remotion.Linq.QueryModelVisitorBase.VisitBodyClauses(ObservableCollection'1 bodyClauses, QueryModel queryModel)
   at Remotion.Linq.QueryModelVisitorBase.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.SqlTranslatingExpressionVisitor.VisitSubQuery(SubQueryExpression expression)
   at Npgsql.EntityFrameworkCore.PostgreSQL.Query.ExpressionVisitors.NpgsqlSqlTranslatingExpressionVisitor.VisitSubQuery(SubQueryExpression expression)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.SqlTranslatingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.VisitWhereClause(WhereClause whereClause, QueryModel queryModel, Int32 index)
   at Remotion.Linq.QueryModelVisitorBase.VisitBodyClauses(ObservableCollection'1 bodyClauses, QueryModel queryModel)
   at Remotion.Linq.QueryModelVisitorBase.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.CreateQueryExecutor[TResult](QueryModel queryModel)
--- End of stack trace from previous location where exception was thrown ---
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](Expression query, IQueryModelGenerator queryModelGenerator, IDatabase database, IDiagnosticsLogger'1 logger, Type contextType)
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func'1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
   at Remotion.Linq.QueryableBase'1.GetEnumerator()
   at System.Linq.Enumerable.WhereEnumerableIterator'1.ToList()
   at System.Linq.Enumerable.ToList[TSource](IEnumerable'1 source)
   at SWSF.BusinessObjects.SqlBo'2.FindEntities()
ghost commented 5 years ago

So I looked more into it and couldn't get it to work in a simple console app. I tried again with the PostgreSQL database and logged the expression query. I also tried to get the sql-statement from the query but that threw the above pasted error.

Query string:

Context.Users.Where("UserGroups.Any(User.GetDisplayName(true, true, false).Contains(\"2\"))")

Resulting query expression:

Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[Model.User])
.Where(Param_0 => Param_0.UserGroups.Any(Param_1 => Param_1.User.GetDisplayName(True, True, False).Contains("2"))

For other filtering strings I can get the sql query, but if I try to add the users "GetDisplayName"-function in this particular level of the query, it breaks when I try to retrieve the sql.

Other query:

Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[Model.User])
.Where(Param_0 => Param_0.UserGroups.Any(Param_1 => (Param_1.User.GivenName != null))

Translates to sql:

SELECT u."Id", u."Discriminator", u."Email", u."FamilyName", u."GivenName", u."Nickname"
FROM "Users" AS u
WHERE (u."Discriminator" = 'User') AND EXISTS (
SELECT 1
FROM "UserGroups" AS u0
INNER JOIN "Users" AS "u.User" ON u0."UserId" = "u.User"."Id"
WHERE ("u.User"."Discriminator" IN ('User', 'BaseUser') AND "u.User"."GivenName" IS NOT NULL) AND (u."Id" = u0."UserId"))

So I think that this is a problem of the PostgreSQL provider, what do you think?

JonathanMagnan commented 4 years ago

Hello @SolidWhiteSven ,

I will close this issue. As you pointed out it looks the problem is with the PostgreSQL provider and not the library.

Best Regards,

Jon


Performance Libraries context.BulkInsert(list, options => options.BatchSize = 1000); Entity Framework ExtensionsEntity Framework ClassicBulk OperationsDapper Plus

Runtime Evaluation Eval.Execute("x + y", new {x = 1, y = 2}); // return 3 C# Eval FunctionSQL Eval Function