1

I'm trying to query some data and projecting to a class with fewer properties by sending an expression (C# mongo driver version 2.7.3). I am trying to understand why a specific expression fails. The failure greatly limits the user from writing a common projection and forces him to write the projection inline in every call. This is a simplified example:

private IMongoCollection<MyOriginalClass> _collection;

class MyOriginalClass // imagine this class has many more properties
{
  public int ID { get; set; }
}

class MyProjectedClass
{
  public int ID { get; set; }
}

void DoWork()
{
  var data1 = GetData(lib => new MyProjectedClass { ID = lib.ID }); // succeeds
  var data2 = GetData(lib => ToProjected(lib)); // Fails in mongo driver: Index was out of range. Must be non-negative and less than the size of the collection.Parameter name: index
}

IEnumerable<MyProjectedClass> GetData(Expression<Func<MyOriginalClass, MyProjectedClass>> projection)
{       
  return _collection
      .Aggregate()
      .Project(Builders<MyOriginalClass>.Projection.Expression(projection))
      .ToList();
}

MyProjectedClass ToProjected(MyOriginalClass orig)
{
    return new MyProjectedClass {ID = orig.ID};
}
3
  • Please familiarize with stackoverflow.com/questions/793571/… Commented Apr 15, 2019 at 14:01
  • But the second one is still an Expression and not a Func (it's not compiled). Commented Apr 15, 2019 at 14:21
  • That's right. I've update my answer to be more specific Commented Apr 15, 2019 at 14:43

1 Answer 1

2

First (succeeded) usage is an expression which mongo driver can look into at the runtime to become to know that ID = lib.ID. Specifically here is NewExpression.

E.g. Visual Studio allow for expression visualization under debugger, and for first one it shows:

.Lambda #Lambda1<System.Func`2[ConsoleApp1.Program+MyOriginalClass,ConsoleApp1.Program+MyProjectedClass]>(ConsoleApp1.Program+MyOriginalClass $lib)
{
    .New ConsoleApp1.Program+MyProjectedClass(){
        ID = $lib.ID
    }
}

Second (failed) usage is an expression with just a call to ToProjected, ToProjected is being compiled into IL, and at the runtime mongo driver cannot retrieve the knowledge that ID = lib.ID (at least not in such an easy way as with expressions). Specifically here is MethodCallExpression. And the visualization of the second expression is:

.Lambda #Lambda1<System.Func`2[ConsoleApp1.Program+MyOriginalClass,ConsoleApp1.Program+MyProjectedClass]>(ConsoleApp1.Program+MyOriginalClass $lib)
{
    .Call ConsoleApp1.Program.ToProjected($lib)
}

ToProject could be re-written as:

Expression<Func<MyOriginalClass, MyProjectedClass>> ToProjected()
{
    return lib => new MyProjectedClass { ID = lib.ID };
}

And used as:

var data2 = GetData(ToProjected());
Sign up to request clarification or add additional context in comments.

2 Comments

So there is no difference between a compiled expression (Func) and a MethodCallExpression? MethodCallExpression cannot be traversed?
Well it's kind of no difference, MethodCallExpression just calls a compiled code, as expression tree it's very shallow, and provides little info at the runtime. In contrast NewExpression could be a bigger expression tree with more info. And yes, MethodCallExpression cannot be traversed (without decompiling)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.