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

Cannot reuse parameter expression in LINQ chain #2985

Closed
Arithmomaniac opened this issue Jan 17, 2022 · 0 comments · Fixed by #3826
Closed

Cannot reuse parameter expression in LINQ chain #2985

Arithmomaniac opened this issue Jan 17, 2022 · 0 comments · Fixed by #3826

Comments

@Arithmomaniac
Copy link

Describe the bug
Reusing the ParameterExpression on the left-hand-side of a lambda in an IQueryable chain throws an ArgumentException while parsing the chain. This can be an issue when manipulating LINQ expressions with things like NeinLinq's predicate translator.

Mitigation
Clone any expressions before reuse, or inline them instead.

Illustration

async Task<List<Pet>> GetPetsByOwner(IEnumerable<Expression<Func<Person, bool>>> ownerFilters)
{
    Expression<Func<Pet, Person>> ownerExpression = pet => pet.Owner;
    IQueryable<Pet> petCollectionQueryable = /*...*/;
    foreach (var ownerFilter in ownerFilters)
    {
        petCollectionQueryable = petCollectionQueryable.Where(ownerFilter.Translate().To(ownerExpression));
    }

    return await petCollectionQueryable.ToListAsync();
}

in this case, the same "pet" parameter expression will be used by all the built filters on petCollectionQueryable (e.g. pet => pet.Owner.Name == "Joe", pet => pet.Owner.Age > 20), unlike if the filters had been written manually. This will cause the exception detailed below in "To Reproduce".

To Reproduce
Navigate to this code in the solution:

public void TestOrderByTranslation()
{
List<LinqTestInput> inputs = new List<LinqTestInput>();

And then stick the following code after it:

System.Linq.Expressions.Expression<Func<Family, bool>> predicate = f => f.Int == 5;
inputs.Add(new LinqTestInput("Where -> OrderBy with same predicate", b => getQuery(b).Where(predicate).OrderBy(predicate)));

Debugging will return this exception:

System.ArgumentException
  HResult=0x80070057
  Message=An item with the same key has already been added. Key: f
  Source=System.Private.CoreLib
  StackTrace:
   at System.ThrowHelper.ThrowAddingDuplicateWithKeyArgumentException[T](T key)
   at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
   at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
   at Microsoft.Azure.Cosmos.Linq.ParameterSubstitution.AddSubstitution(ParameterExpression parameter, Expression with) in C:\_SRC\azure-cosmos-dotnet-v3\Microsoft.Azure.Cosmos\src\Linq\TranslationContext.cs:line 326

The full stack trace continues with:

Microsoft.Azure.Cosmos.Linq.TranslationContext.PushParameter(System.Linq.Expressions.ParameterExpression parameter, bool shouldBeOnNewQuery)
Microsoft.Azure.Cosmos.Linq.ExpressionToSql.VisitNonSubqueryScalarExpression(System.Linq.Expressions.Expression expression, System.Collections.ObjectModel.ReadOnlyCollection<System.Linq.Expressions.ParameterExpression> parameters, Microsoft.Azure.Cosmos.Linq.TranslationContext context)
Microsoft.Azure.Cosmos.Linq.ExpressionToSql.VisitScalarExpression(System.Linq.Expressions.Expression expression, System.Collections.ObjectModel.ReadOnlyCollection<System.Linq.Expressions.ParameterExpression> parameters, Microsoft.Azure.Cosmos.Linq.TranslationContext context)
Microsoft.Azure.Cosmos.Linq.ExpressionToSql.VisitScalarExpression(System.Linq.Expressions.LambdaExpression lambda, Microsoft.Azure.Cosmos.Linq.TranslationContext context)
Microsoft.Azure.Cosmos.Linq.ExpressionToSql.VisitOrderBy(System.Collections.ObjectModel.ReadOnlyCollection<System.Linq.Expressions.Expression> arguments, bool isDescending, Microsoft.Azure.Cosmos.Linq.TranslationContext context)
Microsoft.Azure.Cosmos.Linq.ExpressionToSql.VisitMethodCall(System.Linq.Expressions.MethodCallExpression inputExpression, Microsoft.Azure.Cosmos.Linq.TranslationContext context)
Microsoft.Azure.Cosmos.Linq.ExpressionToSql.Translate(System.Linq.Expressions.Expression inputExpression, Microsoft.Azure.Cosmos.Linq.TranslationContext context)
Microsoft.Azure.Cosmos.Linq.ExpressionToSql.TranslateQuery(System.Linq.Expressions.Expression inputExpression, System.Collections.Generic.IDictionary<object, string> parameters, Microsoft.Azure.Cosmos.CosmosLinqSerializerOptions linqSerializerOptions)
Microsoft.Azure.Cosmos.Linq.SqlTranslator.TranslateQuery(System.Linq.Expressions.Expression inputExpression, Microsoft.Azure.Cosmos.CosmosLinqSerializerOptions linqSerializerOptions, System.Collections.Generic.IDictionary<object, string> parameters)
Microsoft.Azure.Cosmos.Linq.DocumentQueryEvaluator.HandleMethodCallExpression(System.Linq.Expressions.MethodCallExpression expression, System.Collections.Generic.IDictionary<object, string> parameters, Microsoft.Azure.Cosmos.CosmosLinqSerializerOptions linqSerializerOptions)
Microsoft.Azure.Cosmos.Linq.DocumentQueryEvaluator.Evaluate(System.Linq.Expressions.Expression expression, Microsoft.Azure.Cosmos.CosmosLinqSerializerOptions linqSerializerOptions, System.Collections.Generic.IDictionary<object, string> parameters)
This issue was closed.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: Done
Development

Successfully merging a pull request may close this issue.

5 participants