Reputation: 884
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, IEnumerable
1 arguments) at MongoDB.Driver.Linq.Processors.MethodInfoMethodCallBinder
1.Bind(PipelineExpression pipeline, TBindingContext bindingContext, MethodCallExpression node, IEnumerable1 arguments) at MongoDB.Driver.Linq.Processors.CompositeMethodCallBinder
1.Bind(PipelineExpression pipeline, TBindingContext bindingContext, MethodCallExpression node, IEnumerable1 arguments) at MongoDB.Driver.Linq.Processors.PipelineBinderBase
1.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, IBsonSerializer
1 parameterSerializer, IBsonSerializerRegistry serializerRegistry) at MongoDB.Driver.MongoCollectionImpl1.CreateFindOperation[TProjection](FilterDefinition
1 filter, FindOptions2 options) at MongoDB.Driver.MongoCollectionImpl
1.FindAsync[TProjection](IClientSessionHandle session, FilterDefinition1 filter, FindOptions
2 options, CancellationToken cancellationToken) at MongoDB.Driver.MongoCollectionImpl1.<>c__DisplayClass43_0
1.b__0(IClientSessionHandle session) at MongoDB.Driver.MongoCollectionImpl1.UsingImplicitSessionAsync[TResult](Func
2 funcAsync, CancellationToken cancellationToken) at MongoDB.Driver.IAsyncCursorSourceExtensions.ToListAsync[TDocument](IAsyncCursorSource1 source, CancellationToken cancellationToken) at ...Repository.MongoRepository
2.FindAsync(Expression1 predicate, SortOrder
1[] sort, Nullable1 page, Nullable
1 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
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
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