[CSHARP-2894] Dictionary support for translation .Item["key"] Created: 13/Jan/20  Updated: 27/Oct/23  Resolved: 17/Jan/20

Status: Closed
Project: C# Driver
Component/s: Linq
Affects Version/s: 2.10.0
Fix Version/s: None

Type: Improvement Priority: Major - P3
Reporter: Roel Bruins Assignee: Dmitry Lukyanov (Inactive)
Resolution: Works as Designed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

We're using the [Microsoft Expression|https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/expression-trees/how-to-use-expression-trees-to-build-dynamic-queries] to query the mongoDb dynamically using the C# Driver.

So far this was working out pretty well, but in the following scenario we're running into trouble:

// C# Model
public class Item{
  public Name { get; set; }
  public Dictionary<string, Property> Properties { get; set; }
  ...
}
 
public class Property {
  public dynamic Value { get; set; }
  ...
}

Our configuration translates this into the following JSON in the Database (using the fluent class mapping)

// JSON in MongoDB
{
  "Name": "Door",
  "Properties": {
    "Color": {
      "Value": "Red",
      ...
    },
    "Height": {
      "Value": 300,
      ...
    },
    ...
  },
  ...
}

We can query it in MongoDB shell easily as:

// Mongo shell
db.getCollection('test_collection').find({'Properties.Color.Value': 'Red'})

And even in c# we can query it in link as:

// C# Linq query
test_collection
  .AsQueryable()
  .Where(x => x.Properties["Color"].Value as string == "Red")
  .SingleOrDefault();

So far so good, this is all working nice and properly, but now comes the tricky part. The dynamic expression:

// Don't you mind this big lumb of code, but might be usefull for other readers, cause it took us a while to craft this:
 
var parameterExpression = Expression.Parameter(typeof(T), "x");
var properties = Expression.Property(parameterExpression, "Properties");
var propertyInfo = typeof(Dictionary<string,EntityProperty>).GetProperty("Item");
var arguments = new List<Expression> { Expression.Constant("Color") };
var indexer = Expression.MakeIndex(properties, propertyInfo, arguments);
var valueProperty = Expression.Property(indexer, "Value");
var compareValue = Expression.Constant("Red");
var converted = Expression.Convert(valueProperty, typeof(string));
var expression = Expression.MakeBinary(ExpressionType.Equal, converted, compareValue);
var lambda =  Expression.Lambda<Func<T, bool>>(expression, parameterExpression);
 
// What this basicially does, it generates a lambda expression dynamically,
// however the format is the following:
 
// x => x.Properties.Item["Color"].Value == "Red"

If we pass this query to a local List<Item> the objects are filtered perfectly,
however when we pass this to mongodb, it gives me an error on {document}{Properties}.Item

test_collection   //IMongoCollection<Item>
  .AsQueryable()
  .Where(lambda)
  .SingleOrDefault();

Question: Should MongoDB c# driver support dynamically created Expressions for Dictionaries?

------------------------------------------
Notes:

  • Dynamic expresions are working fine without Dictionaries (i.e. we filter on Name)
  • The .Item is the only Way to build dynamic expressions on a dictionary
  • Not sure if this is a feature request, or a bug...


 Comments   
Comment by Dmitry Lukyanov (Inactive) [ 17/Jan/20 ]

Hello roel.bruins@intar.nl,

please try the following code:

            var parameterExpression = Expression.Parameter(typeof(Item), "x");
            var properties = Expression.Property(parameterExpression, "Properties");
            var methodInfo = typeof(Dictionary<string, Property>).GetMethod("get_Item");
            var arguments = new List<Expression> { Expression.Constant("Color") };
            var indexer = Expression.Call(properties, methodInfo, arguments);
            var valueProperty = Expression.Property(indexer, "Value");
            var compareValue = Expression.Constant("Red");
            var converted = Expression.Convert(valueProperty, typeof(string));
            var expression = Expression.MakeBinary(ExpressionType.Equal, converted, compareValue);
            var lambda = Expression.Lambda<Func<Item, bool>>(expression, parameterExpression);

Generated at Wed Feb 07 21:43:48 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.