[CSHARP-3991] Cannot pass expression to FindSync when document and projection are different Created: 10/Dec/21  Updated: 31/Mar/22

Status: Backlog
Project: C# Driver
Component/s: None
Affects Version/s: None
Fix Version/s: None

Type: Bug Priority: Major - P3
Reporter: David Golub Assignee: Unassigned
Resolution: Unresolved Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified


 Description   

Summary

Please provide a clear and concise description of the bug.
The signature of the FindSync extension method restricts the document and projection so that they must be the same. This prevents passing in an expression for the filter when they're different.

Please provide the version of the driver. If applicable, please provide the MongoDB server version and topology (standalone, replica set, or sharded cluster).

2.14.1

How to Reproduce

Steps to reproduce. If possible, please include a Short, Self Contained, Correct (Compilable), Example.
The following code won't compile:

using IAsyncCursor<TestProjection> cursor =
    coll.FindSync(
        new ExpressionFilterDefinition<TestDocument>(x => x.A == 1),
        new FindOptions<TestDocument, TestProjection>()
        {
            Projection = Builders<TestDocument>.Projection.Include(x => x.B)
        });

Additional Background

Please provide any additional background information that may be helpful in diagnosing the bug.

This can probably be fixed by changing IMongoCollectionExtensions.cs:943 to read as follows:

public static IAsyncCursor<TProjection> FindSync<TDocument, TProjection>(this IMongoCollection<TDocument> collection, Expression<Func<TDocument, bool>> filter, FindOptions<TDocument, TProjection> options = null, CancellationToken cancellationToken = default(CancellationToken))



 Comments   
Comment by Robert Stam [ 17/Dec/21 ]

Notes for the future:

Looks like the new overloads could co-exist with the current overloads without ambiguity if we replaced `FindOptions<TDocument, TDocument>` with `FindOption<TDocument>`.

That might be a slight breaking change, but not hard to adjust to. I think it would only be a runtime breaking change, not a compile time breaking change.

Note that `FindOptions<TDocument>` derives from `FindOptions<TDocument, TDocument>` with no further properties added (it's just a quick way to create FindOptions when not using a projection).

Comment by David Golub [ 17/Dec/21 ]

Fair enough, then. Maybe in a future release when the major version changes. For now, I guess go ahead and close the ticket. Thanks for all your effort here.

Comment by Robert Stam [ 17/Dec/21 ]

It doesn't seem like it's possible to add new overloads without it being a breaking change.

While the new overloads make your example compile as-is (without the cast), they cause other variations to now fail to compile. The compilation error is due to ambiguity in some cases between the existing overloads and the new overloads.

Look like using the cast is the only available solution here.

Comment by Robert Stam [ 17/Dec/21 ]

By the way, since the problem is that the compiler can't tell if the lambda expression is a delegate or an `Expression`, the most straightforward workaround is to simply use a cast to tell the compiler that it is an `Expression`:

(Expression<Func<TestDocument, bool>>)(x => x.A == 1)

Comment by Robert Stam [ 17/Dec/21 ]

Thanks for the update. Now I can reproduce.

It is related to using a lambda expression.  In this situation the compiler can't infer whether the lambda expression should be a delegate or an `Expression`.

I will look into adding new overloads, but need to make sure that adding new overloads isn't a breaking change.

Comment by David Golub [ 17/Dec/21 ]

My mistake: I included the code with the change to get it to compile. The code that won't compile is:

using IAsyncCursor<TestProjection> cursor =
    coll.FindSync(
        x => x.A == 1,
        new FindOptions<TestDocument, TestProjection>()
        {
            Projection = Builders<TestDocument>.Projection.Include(x => x.B)
        });

Comment by Robert Stam [ 17/Dec/21 ]

I am unable to reproduce this. The code you provided compiles without error for me.

See the repro (with many other tests as well for all possible combinations of parameters) here:

https://github.com/rstam/mongo-csharp-driver/blob/csharp3991/tests/MongoDB.Driver.Tests/Jira/CSharp3991Tests.cs

Note that I did get a runtime error. I had to change the projection to:

Projection = Builders<C>.Projection.Include(x => x.X).Exclude(x => x.Id) // note: I'm using slightly different class and field names

to exclude the `_id` field from the projection.

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