Azure / azure-cosmos-dotnet-v3

.NET SDK for Azure Cosmos DB for the core SQL API
MIT License
732 stars 490 forks source link

NullReferenceException when using GetItemLinqQueryable without partition key in LINQ filters #2479

Closed azzimuth closed 2 years ago

azzimuth commented 3 years ago

Describe the bug When I try to get items from the database using the GetItemLinqQueryable method without partition key in LINQ filters, I get NullReferenceException at await cosmosQuery.CountAsync();. As soon as I add the partition key to LINQ filters, everything works.

To Reproduce Sample code here.

Expected behavior Using GetItemLinqQueryable without LINQ filter for partition key does not throw an exception. The queryable returns the items matching the filters.

Actual behavior Using GetItemLinqQueryable without LINQ filter for partition key throws an exception.

Exception details:

System.Reflection.TargetInvocationException:` Exception has been thrown by the target of an invocation.
 ---> System.NullReferenceException: Object reference not set to an instance of an object.
 at lambda_method(Closure )
 --- End of inner exception stack trace ---
 at System.RuntimeMethodHandle.InvokeMethod(Object target, Object[] arguments, Signature sig, Boolean constructor, Boolean wrapExceptions)
 at System.Reflection.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
 at System.Delegate.DynamicInvokeImpl(Object[] args)
   at System.Delegate.DynamicInvoke(Object[] args)
 at Microsoft.Azure.Cosmos.Linq.SubtreeEvaluator.EvaluateConstant(Expression expression)
 at Microsoft.Azure.Cosmos.Linq.SubtreeEvaluator.Visit(Expression expression)
 at System.Linq.Expressions.ExpressionVisitor.VisitBinary(BinaryExpression node)
   at System.Linq.Expressions.BinaryExpression.Accept(ExpressionVisitor visit
or)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.Azure.Cosmos.Linq.SubtreeEvaluator.Visit(Expression expression)
   at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 node)
   at System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.Azure.Cosmos.Linq.SubtreeEvaluator.Visit(Expression expression)
   at System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression node)
   at System.Linq.Expressions.UnaryExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.Azure.Cosmos.Linq.SubtreeEvaluator.Visit(Expression expression)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)

  at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.Azure.Cosmos.Linq.SubtreeEvaluator.Visit(Expression expression)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.Azure.Cosmos.Linq.SubtreeEvaluator.Visit(Expression expression)
   at Microsoft.Azure.Cosmos.Linq.SubtreeEvaluator.Evaluate(Expression expression)
   at Microsoft.Azure.Cosmos.Linq.ConstantEvaluator.PartialEval(Expression expression, Func`2 fnCanBeEvaluated)
   at Microsoft.Azure.Cosmos.Linq.ConstantEvaluator.PartialEval(Expression expression)
   at Microsoft.Azure.Cosmos.Li
nq.SqlTranslator.TranslateQuery(Expression inputExpression, CosmosLinqSerializerOptions linqSerializerOptions, IDictionary`2 parameters)
   at Microsoft.Azure.Cosmos.Linq.DocumentQueryEvaluator.HandleMethodCallExpression(MethodCallExpression expression, IDictionary`2 parameters, CosmosLinqSerializerOptions linqSerializerOptions)
   at Microsoft.Azure.Cosmos.Linq.DocumentQueryEvaluator.Evaluate(Expression expression, CosmosLinqSerializerOptions linqSerializerOptions, IDictionary`2 parameters)
   at Microsoft.Azure.Cosmos.Linq.CosmosLinqQuery`1.CreateFeedIterator(Boolean isContinuationExpected)
   at Microsoft.Azure.Cosmos.Linq.CosmosLinqQuery`1.AggregateResultAsync(CancellationToken cancellationToken)
   at CosmosTest.Service.CosmosDbClient.GetLeadsAsync(LeadsSearch search) in C:\\Project\\CosmosTest\\Service\\CosmosDbClient.cs:line 33

Environment summary SDK Version: 3.18.0 OS Version (e.g. Windows, Linux, MacOSX): Edition Windows 10 Pro Version 20H2 OS build 19042.985 Experience Windows Feature Experience Pack 120.2212.2020.0

j82w commented 3 years ago

@azzimuth can you add the exception.ToString() to the description?

azzimuth commented 3 years ago

@j82w, done

j82w commented 3 years ago

It seems to be incorrectly handling the scenario if a function is passed in for a binary comparison that is not from the passed in delegate search.MarketCode.ToUpper().

queryable = queryable.Where(lead => lead.LeadRequest.CountryCode.ToUpper() == search.MarketCode.ToUpper());

As a temporary workaround until a fix is deployed you can do the following.

string searchMarkCode = search.MarketCode.ToUpper();
queryable = queryable.Where(lead => lead.LeadRequest.CountryCode.ToUpper() == searchMarkCode);
azzimuth commented 3 years ago

Great, it works. Thanks!

j82w commented 3 years ago

I would always recommend passing the partition key in the request options when possible. It guarantees that the query is restricted to a single partition. Without the partition key it's possible it will do a cross partition query which will be significantly slower and cost more RUs.

azzimuth commented 3 years ago

@j82w, yes, we will consider changing the partition key to the other field.