Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

InvalidCastException when trying to use FreeText with a local variable as the language argument #13315

Closed
thomaslevesque opened this issue Sep 13, 2018 · 8 comments · Fixed by #20308
Labels
area-query closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-enhancement
Milestone

Comments

@thomaslevesque
Copy link
Member

thomaslevesque commented Sep 13, 2018

When I try to use a local variable for the language argument of the FreeText method, I get the following exception:

Exception message: System.InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.PrimitiveParameterExpression`1[System.Int32]' to type 'System.Linq.Expressions.ConstantExpression'.
Stack trace:
   at Microsoft.EntityFrameworkCore.SqlServer.Query.ExpressionTranslators.Internal.SqlServerFreeTextMethodCallTranslator.Translate(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.RelationalCompositeMethodCallTranslator.<>c__DisplayClass5_0.<Translate>b__0(IMethodCallTranslator translator)
   at System.Linq.Enumerable.SelectListIterator`2.MoveNext()
   at System.Linq.Enumerable.TryGetFirst[TSource](IEnumerable`1 source, Func`2 predicate, Boolean& found)
   at System.Linq.Enumerable.FirstOrDefault[TSource](IEnumerable`1 source, Func`2 predicate)
   at Microsoft.EntityFrameworkCore.Query.ExpressionTranslators.RelationalCompositeMethodCallTranslator.Translate(MethodCallExpression methodCallExpression, IModel model)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.SqlTranslatingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Remotion.Linq.Parsing.ThrowingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.SqlTranslatingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.SqlTranslatingExpressionVisitor.VisitBinary(BinaryExpression expression)
   at Microsoft.EntityFrameworkCore.SqlServer.Query.ExpressionVisitors.Internal.SqlServerSqlTranslatingExpressionVisitor.VisitBinary(BinaryExpression binaryExpression)
   at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Remotion.Linq.Parsing.ThrowingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.SqlTranslatingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.SqlTranslatingExpressionVisitor.VisitBinary(BinaryExpression expression)
   at Microsoft.EntityFrameworkCore.SqlServer.Query.ExpressionVisitors.Internal.SqlServerSqlTranslatingExpressionVisitor.VisitBinary(BinaryExpression binaryExpression)
   at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Remotion.Linq.Parsing.ThrowingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.SqlTranslatingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.SqlTranslatingExpressionVisitor.VisitBinary(BinaryExpression expression)
   at Microsoft.EntityFrameworkCore.SqlServer.Query.ExpressionVisitors.Internal.SqlServerSqlTranslatingExpressionVisitor.VisitBinary(BinaryExpression binaryExpression)
   at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Remotion.Linq.Parsing.ThrowingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.SqlTranslatingExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.SqlTranslatingExpressionVisitor.VisitBinary(BinaryExpression expression)
   at Microsoft.EntityFrameworkCore.SqlServer.Query.ExpressionVisitors.Internal.SqlServerSqlTranslatingExpressionVisitor.VisitBinary(BinaryExpression binaryExpression)
   at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Remotion.Linq.Parsing.ThrowingExpressionVisitor.Visit(Expression 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.Clauses.WhereClause.Accept(IQueryModelVisitor visitor, 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.CreateAsyncQueryExecutor[TResult](QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileAsyncQuery[TResult](QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileAsyncQueryCore[TResult](Expression query, IQueryModelGenerator queryModelGenerator, IDatabase database)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass22_0`1.<CompileAsyncQuery>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddAsyncQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileAsyncQuery[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.System.Collections.Generic.IAsyncEnumerable<TResult>.GetEnumerator()
   at System.Linq.AsyncEnumerable.Aggregate_[TSource,TAccumulate,TResult](IAsyncEnumerable`1 source, TAccumulate seed, Func`3 accumulator, Func`2 resultSelector, CancellationToken cancellationToken)
   at MyProject.SomeClass.SomeMethod(string searchTerm) in C:\Projects\MyProject\SomeClass.cs:line 71

Steps to reproduce

int lcid = CultureInfo.CurrentCulture.LCID;
var results = await _dbContext.Items
    .Where(i => EF.Functions.FreeText(i.Text, searchTerm, lcid))
    .ToListAsync();

Using a literal instead of the lcid variable works fine.

The problem seems to be on this line:

https://github.com/aspnet/EntityFrameworkCore/blob/7b11733e091e937ea6f616ecf56ebc6fcb11955f/src/EFCore.SqlServer/Query/ExpressionTranslators/Internal/SqlServerFreeTextMethodCallTranslator.cs#L66

The translator assumes the expression to be a ConstantExpression, which means it can only work with a hard-coded literal value.

Further technical details

EF Core version: 2.1.1
Database Provider: Microsoft.EntityFrameworkCore.SqlServer (2.1.1)
Operating system: Windows 10 v1803
IDE: VS2017 v15.8.1

@thomaslevesque
Copy link
Member Author

In the original expression, the argument is a FieldExpression (the lcid captured local variable) off a ConstantExpression (the object holding the captured locals). But by the time it reaches the SqlServerFreeTextMethodCallTranslator class, it has been changed to a PrimitiveParameterExpression, so I'm not sure if it's possible to retrieve the value...

@thomaslevesque
Copy link
Member Author

thomaslevesque commented Sep 13, 2018

Workaround: fixup the expression to replace references to a local variable with the value of that variable.

static class Extensions
{
    public static IQueryable<TSource> ReplaceLocalWithItsValue<TSource, TValue>(this IQueryable<TSource> source, Expression<Func<TValue>> localExpression, TValue value)
    {
        if (localExpression.Body is MemberExpression memberExpr && memberExpr.Expression is ConstantExpression constant)
        {
            var visitor = new MemberReplacementVisitor<TValue>(constant.Value, memberExpr.Member, value);
            var newExpression = visitor.Visit(source.Expression);
            return source.Provider.CreateQuery<TSource>(newExpression);
        }

        throw new ArgumentException("Invalid local expression");
    }

    private class MemberReplacementVisitor<TValue> : ExpressionVisitor
    {
        private readonly object _instance;
        private readonly MemberInfo _member;
        private readonly TValue _replacementValue;

        public MemberReplacementVisitor(object instance, MemberInfo member, TValue replacementValue)
        {
            _instance = instance;
            _member = member;
            _replacementValue = replacementValue;
        }

        protected override Expression VisitMember(MemberExpression node)
        {
            if (node.Expression is ConstantExpression constant && Equals(constant.Value, _instance) && Equals(node.Member, _member))
                return Expression.Constant(_replacementValue, typeof(TValue));

            return base.VisitMember(node);
        }
    }
}

Usage:

int lcid = CultureInfo.CurrentCulture.LCID;
var results = await _dbContext.Items
    .Where(i => EF.Functions.FreeText(m.Text, searchTerm, lcid))
    .ReplaceLocalWithItsValue(() => lcid, lcid)
    .ToListAsync();

@ajcvickers
Copy link
Member

Triage: moving this to the backlog to look into a general way of allowing values like this to not be parameterized.

@smitpatel to link related issue.

@ajcvickers ajcvickers added this to the Backlog milestone Sep 17, 2018
@smitpatel
Copy link
Member

#12764

@smitpatel
Copy link
Member

We need to decorate FreeText method's argument as NotParameterizedAttribute to avoid having a parameter there.

thomaslevesque added a commit to thomaslevesque/efcore that referenced this issue Mar 17, 2020
To support non-literal value for the argument

Fixes dotnet#13315
@thomaslevesque
Copy link
Member Author

@smitpatel I just sent a PR for this (#20308)

smitpatel pushed a commit to thomaslevesque/efcore that referenced this issue Mar 17, 2020
@smitpatel smitpatel added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Mar 17, 2020
@smitpatel smitpatel modified the milestones: Backlog, 5.0.0 Mar 17, 2020
@ajcvickers ajcvickers modified the milestones: 5.0.0, 5.0.0-preview3 Mar 31, 2020
@ajcvickers ajcvickers modified the milestones: 5.0.0-preview3, 5.0.0 Nov 7, 2020
@shahzadq77
Copy link

shahzadq77 commented Aug 7, 2022

@smitpatel @ajcvickers August 2022 - I'm getting this error, using this code, .net 3.0:

My azure instances are in 3.1 - The workaround listed here wouldn't work for my scenario, as I'm converting a SQL stored procedure using this method.

image

@roji
Copy link
Member

roji commented Aug 7, 2022

@shahzadq77 this issue was about the use of the FreeText function with a parameter, but the code above doesn't seem to use FreeText. Please open a new issue with a runnable code sample and the full exception details.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-query closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. customer-reported type-enhancement
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants