Renato Pereira
Renato Pereira

Reputation: 884

C# - Unable to cast object of type 'System.Linq.Expressions.ConstantExpression' to type 'System.Linq.Expressions.LambdaExpression'

I was trying to retrieving data from a collection as follow

var results = await repository.FindAsync(GetFilter(ids));

private System.Linq.Expressions.Expression<Func<SomeEntity, bool>> GetFilter(IEnumerable<long> someIds)
{
    return x => someIds.Contains(x.Id) &&
                x.subscriptions
               .Where(s => s.servicePlans.Contains((int)ServicePlanEnum.Service1)).Any();
}

but I got the following exception

System.InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.ConstantExpression' to type 'System.Linq.Expressions.LambdaExpression'.

Where am I wrong?

EDIT:

Subscription object looks like:

subscription :
{
    prop1 : string,
    prop2 : int,
    servicePlans : int[]
}

FindAsync definition:

protected IMongoCollection<TEntity> Collection => database.GetCollection<TEntity>(collectionName);
private IFindFluent<TEntity, TEntity> Find(Expression<Func<TEntity, bool>> predicate) => Collection.Find(predicate);
public virtual async Task<ICollection<TEntity>> FindAsync(Expression<Func<TEntity, bool>> predicate, SortOrder<TEntity>[] sort = null, int? page = null, int? limit = null)
{
    var query = Find(predicate);

    return await
        query
            .Skip(page == null && limit == null ? null : (page - 1) * limit)
            .Limit(limit)
            .Sort(sort)
            .ToListAsync();
}

The error is caused by the following condition

x.subscriptions.Where(s => s.servicePlans.Contains((int)ServicePlanEnum.Service1)).Any();

EDIT:

stacktrace:

Unable to cast object of type 'System.Linq.Expressions.ConstantExpression' to type 'System.Linq.Expressions.LambdaExpression'. System.InvalidCastException: Unable to cast object of type 'System.Linq.Expressions.ConstantExpression' to type 'System.Linq.Expressions.LambdaExpression'. at MongoDB.Driver.Linq.ExpressionHelper.GetLambda(Expression node) at MongoDB.Driver.Linq.Processors.EmbeddedPipeline.MethodCallBinders.AnyBinder.Bind(PipelineExpression pipeline, EmbeddedPipelineBindingContext bindingContext, MethodCallExpression node, IEnumerable1 arguments) at MongoDB.Driver.Linq.Processors.MethodInfoMethodCallBinder1.Bind(PipelineExpression pipeline, TBindingContext bindingContext, MethodCallExpression node, IEnumerable1 arguments) at MongoDB.Driver.Linq.Processors.CompositeMethodCallBinder1.Bind(PipelineExpression pipeline, TBindingContext bindingContext, MethodCallExpression node, IEnumerable1 arguments) at MongoDB.Driver.Linq.Processors.PipelineBinderBase1.BindMethodCall(MethodCallExpression node) at MongoDB.Driver.Linq.Processors.EmbeddedPipeline.EmbeddedPipelineBinder.Bind(Expression node, IBindingContext parent) at MongoDB.Driver.Linq.Processors.SerializationBinder.VisitMethodCall(MethodCallExpression node) at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor) at MongoDB.Driver.Linq.Processors.SerializationBinder.Visit(Expression node) at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node) at MongoDB.Driver.Linq.Processors.SerializationBinder.VisitBinary(BinaryExpression node) at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visitor) at MongoDB.Driver.Linq.Processors.SerializationBinder.Visit(Expression node) at MongoDB.Driver.Linq.Translators.PredicateTranslator.Translate[TDocument](Expression1 predicate, IBsonSerializer1 parameterSerializer, IBsonSerializerRegistry serializerRegistry) at MongoDB.Driver.MongoCollectionImpl1.CreateFindOperation[TProjection](FilterDefinition1 filter, FindOptions2 options) at MongoDB.Driver.MongoCollectionImpl1.FindAsync[TProjection](IClientSessionHandle session, FilterDefinition1 filter, FindOptions2 options, CancellationToken cancellationToken) at MongoDB.Driver.MongoCollectionImpl1.<>c__DisplayClass43_01.b__0(IClientSessionHandle session) at MongoDB.Driver.MongoCollectionImpl1.UsingImplicitSessionAsync[TResult](Func2 funcAsync, CancellationToken cancellationToken) at MongoDB.Driver.IAsyncCursorSourceExtensions.ToListAsync[TDocument](IAsyncCursorSource1 source, CancellationToken cancellationToken) at ...Repository.MongoRepository2.FindAsync(Expression1 predicate, SortOrder1[] sort, Nullable1 page, Nullable1 limit) in ...\Infastructure\Repositories\MongoRepository.cs:line 71 at ...Controllers.AccountController.GetCliniciansByClinicAsync(String clinicId, CancellationToken cancellationToken) in ...\Controllers\AccountController.cs:line 94 at Microsoft.AspNetCore.Mvc.Internal.ActionMethodExecutor.TaskOfIActionResultExecutor.Execute(IActionResultTypeMapper mapper, ObjectMethodExecutor executor, Object controller, Object[] arguments) at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeActionMethodAsync() at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeNextActionFilterAsync() at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Rethrow(ActionExecutedContext context) at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.Next(State& next, Scope& scope, Object& state, Boolean& isCompleted) at Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker.InvokeInnerFilterAsync() at Microsoft.AspNetCore.Mvc.Internal.ResourceInvoker.InvokeNextExceptionFilterAsync()

Upvotes: 2

Views: 15495

Answers (2)

Buvy
Buvy

Reputation: 1284

As johnny 5 stated Mongo's C# expression parser is fairly limited. Another thing to point out is you cannot use method groups those will yield a ConstantExpression similar to your example. The key is just leaving the full predicate expression.

Bad

var list = _collection.Find(r => r.Scopes.Any(scopeNames.Contains)).ToListAsync(cancellationToken);

Good

var list = _collection.Find(r => r.Scopes.Any(s => scopeNames.Contains(s)).ToListAsync(cancellationToken);

In the event you have ReSharper or Rider you can disable the code suggestion by putting this comment line right above your code

// ReSharper disable once ConvertClosureToMethodGroup

Upvotes: 3

johnny 5
johnny 5

Reputation: 21005

I haven’t used the linq to Mongodb but it looks like you’re adding unnecessary complexities to your expression.

When you pass an expression, the code needs to be translated to a query in which mongo can handle.

Contains expects an int, in normal c# this is fine.

But as an expression an int is a constant, but you’ve provided an complex expression. e.g casting an enum to an int. Which is a convert expression.

It seems mongodb expression parser is not advanced enough to handle that. So let c# do the cast and pass it directly

var results = await repository.FindAsync(GetFilter(ids));

private System.Linq.Expressions.Expression<Func<SomeEntity, bool>> GetFilter(IEnumerable<long> someIds)
{
    var serviceVal = (int)ServicePlanEnum.Service1;
    return x => someIds.Contains(x.Id) &&
            x.subscriptions
           .Where(s => s.servicePlans.Contains( serviceVal ).Any();
}

Upvotes: 1

Related Questions