[CSHARP-4860] 2.19.1 -> 2.19.2 FindExpressionProjectionDefinition Issue? Trying to select/project/cast Created: 29/Nov/23  Updated: 10/Jan/24  Resolved: 10/Jan/24

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

Type: Bug Priority: Minor - P4
Reporter: Connor Storer Assignee: Robert Stam
Resolution: Works as Designed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Attachments: Zip Archive MongoDBDriver.zip    
Issue Links:
Related
is related to CSHARP-4656 Simplify A : "$A" to A : 1 only on find Closed
is related to CSHARP-4681 InvalidCastException when rendering p... Closed
Documentation Changes Summary:

1. What would you like to communicate to the user about this feature?
2. Would you like the user to see examples of the syntax and/or executable code and its output?
3. Which versions of the driver/connector does this apply to?


 Description   

I'm running into an issue when trying to update my project to from 2.19.1 to latest. I came across a change within 2.19.2 that is causing my code to break, and I'm curious a suggested approach to resolve or work-around. I've commented https://github.com/mongodb/mongo-csharp-driver/pull/1087#issuecomment-1816897272 my issue in further depth.

  • In 2.19.1, projection is public FindExpressionProjectionDefinition
  • In 2.19.2, projection is internal sealed ExpressionProjectionDefinition

var projection = Builders<DocumentEntity>.Projection.Expression(x => new SelectEntity { Name = x.Name, Email = x.Email });

I have a method that is supposed to run my projection using IQueryable via the code below, which no longer works with 2.19.2 and greater.

if (projection is FindExpressionProjectionDefinition<TDocumentEntity, TSelectEntity> expSelect)

Unknown macro: {     return queryable.Select(expSelect.Expression).Cast<TReturnEntity>().ToList(); }

Please advise! Example project attached.



 Comments   
Comment by Robert Stam [ 10/Jan/24 ]

Since we have no plans to make the `ExpressionProjectionDefinition` class public and you already have a workaround using reflection I'm going to close this ticket.

Comment by Connor Storer [ 04/Dec/23 ]

Although great solutions, unfortunately didn't work for my setup . I'm resolving this using reflection to get the Expression from the internal sealed ExpressionProjectionDefinition as this is just within my generic testing framework and will address more in depth in the near future.

Thank you for the time & suggestions, robert@mongodb.com!

Comment by Robert Stam [ 29/Nov/23 ]

Sorry you are affected by this.

You could try replacing:

 

var projection = Builders<DocumentEntity>.Projection.Expression(x => new SelectEntity { Name = x.Name, Email = x.Email });

with

var projection = new FindExpressionProjectionDefinition<DocumentEntity, SelectEntity>(x => new SelectEntity { Name = x.Name, Email = x.Email });

to have projection be a FindExpressionProjectionDefinition like it was in older versions of the driver.

Comment by Connor Storer [ 29/Nov/23 ]

I should have clarified, GetMatchesFromFiltersAsync is a method that I'm using within a mock setup. where i'm currently stuck with the FindOptions with Projection rather than being able to directly access the Expression. 

I'm pasting the current wrapping code which mocks FindAsync. Inside of my GetMatchesFromFiltersAsync I also have custom logic applying internal expressions.

Any insight would be appreciated but understand if I'm at a dead end. Thank you!

collection.Setup(c => c.FindAsync(It.IsAny<FilterDefinition<TDocumentEntity>>(), It.IsAny<FindOptions<TDocumentEntity, TSelectEntity>>(), It.IsAny<CancellationToken>()))
   .Callback<FilterDefinition<TDocumentEntity>, FindOptions<TDocumentEntity, TSelectEntity>, CancellationToken>(async (filter, options, token) =>
   {
       testInstance.FindAsyncCallback?.Invoke(filter, options, token);
       selectMatches = await GetMatchesFromFiltersAsync<TDocumentEntity, TSelectEntity, TDistinctField, TSelectEntity>(filter, options?.Projection, testInstance);       if (options?.Skip.HasValue == true)
       {
           selectMatches = selectMatches.Skip(options.Skip.Value).ToList();
       }
       if (options?.Limit.HasValue == true)
       {
           selectMatches = selectMatches.Take(options.Limit.Value).ToList();
       }
       findSelectResult.Items = selectMatches;
       findSelectResult.ResetIndex();
   })
   .Returns(() =>
   {
       var fs = findSelectResult as IAsyncCursor<TSelectEntity>;
       return Task.FromResult(fs);
   }).Verifiable(); 

 

Comment by Robert Stam [ 29/Nov/23 ]

While FindExpressionProjection is public it is considered an internal class that you should not be using in your code.

You could consider refactoring your GetMatchesFromFiltersAsync method to use an Expression parameter instead of a ProjectionDefinition:

public async Task<List<TReturnEntity>> GetMatchesFromFiltersAsync<TReturnEntity>(Expression<Func<TDocumentEntity, TReturnEntity>> selector)
{
    var queryable = _instance.CollectionDocuments.AsQueryable();
    if (selector != null)
    {
        return queryable.Select(selector).ToList();
    }
    else
    {
        return (List<TReturnEntity>)(object)queryable.ToList();
    }
}

You could also consider using `x => x` as the identity selector instead of null and then you wouldn't need to check if the selector is null and do the odd casting in the else branch.

Note also that this method as written is not actually async, but that has nothing to do with your issue.

You could then call this modified method like this:

var selector = (Expression<Func<DocumentEntity, SelectEntity>>)(x => new SelectEntity { Name = x.Name, Email = x.Email });
 
var selectMatches = await myMongoDbClass.GetMatchesFromFiltersAsync(selector);
foreach (var match in selectMatches)
{
    Console.WriteLine($"Name: {match.Name}, Email: {match.Email}");
}

Using an Expression instead of a a ProjectionDefinition.

Alternatively, if you want to continue using a ProjectionDefinition you would have to modify your code to match the internals of the current version of the driver. I think it would be better to use Expression,

Comment by PM Bot [ 29/Nov/23 ]

Hi connor.storer@kbx.com, thank you for reporting this issue! The team will look into it and get back to you soon.

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