Skip to content

Conversation

sanych-sun
Copy link
Member

No description provided.

@sanych-sun sanych-sun requested a review from rstam May 16, 2023 19:25
var translation = ExpressionToAggregationExpressionTranslator.TranslateLambdaBody(context, expression, inputSerializer, asRoot: true);
var (projectStage, projectionSerializer) = ProjectionHelper.CreateProjectStage(translation);
var simplifiedProjectStage = AstSimplifier.Simplify(projectStage);
var simplifiedProjectStage = AstFindProjectionSimplifier.SimplifyFindProjection(projectStage);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I expected this extra step to be in TranslateExpressionToFindProjection only.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, fixed

@sanych-sun sanych-sun requested a review from rstam May 16, 2023 19:54
Copy link
Contributor

@rstam rstam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like your idea of replacing a regular expression projection definition with a FindExpressionProjectionDefinition when we know we're doing a Find.

@@ -0,0 +1,22 @@
using MongoDB.Driver.Linq.Linq3Implementation.Ast.Expressions;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add copyright

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -0,0 +1,57 @@
using System;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add copyright

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

public void Should_project_to_anonymous_type()
=> AssertProjection(
(Document x) => new { x.A },
"{ A : 1, _id : 0 }");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would refactor these two tests to be:

[Fact]
public void Projection_to_class_should_work()
    => AssertProjection(
        x => new Projection { A = x.A, X = x.B },
        "{ A : 1, X : '$B', _id : 0 }");

[Fact]
public void Projection_to_anonymous_type_should_work()
    => AssertProjection(
        x => new { x.A, X = x.B },
        "{ A : 1, X : '$B', _id : 0 }");

(Note shorter property names)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

(Document x) => new { x.A },
"{ A : 1, _id : 0 }");

[Fact]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would remove this test

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

"{ A : 1, _id : 0 }");

[Fact]
public void Should_project_to_anonymous_type_with_custom_fields()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a test like this also (probably not in this file?):

[Fact]
public void Find_projection_should_simplify_as_expected()
{
    var collection = GetCollection<Document>();
    Expression<Func<Document, Projection>> expression = x => new Projection { A = x.A, X = x.B };
    var projectionDefinition = new ProjectExpressionProjection<Document, Projection>(expression, new ExpressionTranslationOptions());

    var find = collection.Find("{}").Project(projectionDefinition);

    var translatedProjection = TranslateFindProjection(collection, find);
    translatedProjection.Should().Be("{ A : 1, X : '$B', _id : 0 }");

    var results = find.ToList();
}

Copy link
Member Author

@sanych-sun sanych-sun May 19, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added similar test to FindFluentTests without interacting with the database to follow that test class way. And I agree we might want to have the integration test to cover find as well, but as we discussed it we might want to make a decision on how to share that utility methods before creating such test.

"{ A : 1, Num : '$B', _id : 0 }");


private void AssertProjection<TSource, TProjection>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TSource can just be Document. It doesn't have to be a type parameter.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

{
public string Text { get; set; }

public int Number { get; set; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably just A and X for field names. One is the same as Document and the other is not.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@sanych-sun sanych-sun requested a review from rstam May 19, 2023 19:04
Copy link
Contributor

@rstam rstam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let me know if you want to have a Zoom meeting to discuss requested changes.

{
projection = new FindExpressionProjectionDefinition<TDocument, TNewProjection>(expressionProjection.Expression);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is the best place to put this code. There are other ways to do a Find that don't involve the fluent find API.

I think we should add a new RenderForFind to ProjectionDefinition<TSource, TProjection>. Since this is a new method it won't be a breaking change to anyone using or deriving from this class.

This new method allows us to put in one place the logic for rendering for find instead of for $project.

/// <summary>
/// Renders the projection to a <see cref="RenderedProjectionDefinition{TProjection}"/> with additional simplifications to support Find on older servers.
/// </summary>
/// <param name="sourceSerializer">The source serializer.</param>
/// <param name="serializerRegistry">The serializer registry.</param>
/// <param name="linqProvider">The LINQ provider.</param>
/// <returns>A <see cref="RenderedProjectionDefinition{TProjection}"/>.</returns>
public RenderedProjectionDefinition<TProjection> RenderForFind(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
{
    if (this is ExpressionProjectionDefinition<TSource, TProjection> expressionProjectionDefinition)
    {
        var findExpressionProjectionDefinition = new FindExpressionProjectionDefinition<TSource, TProjection>(expressionProjectionDefinition.Expression);
        return findExpressionProjectionDefinition.Render(sourceSerializer, serializerRegistry, linqProvider);
    }

    return Render(sourceSerializer, serializerRegistry, linqProvider);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: I also think we should rename the ProjectExpressionProjection class to simply ExpressionProjectionDefinition.

The new approach is that projections are projections and it is only where they are used (Find vs $project) that determines how they are rendered. We no longer try to make that distinction based on class name alone (because that doesn't work).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Further note: the new RenderForFind methods is the ONE AND ONLY place in the whole code base where it is OK to new up an instance of FindExpressionProjectionDefinition.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CreateFindOperation method in MongoCollectionImpl needs to be modified to call RenderForFind instead of Render.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, TranslateFindProjection in Linq3IntegrationTests needs to be modified to call RenderForFind instead of Render.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, as it was discussed offline via virtual method.

}

[Fact]
public void Find_projection_should_simplify_as_expected()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is probably not the best place to be testing this functionality, because there are other ways to do a Find that don't involve the fluent find API.

The fluent find API just ends up calling FindSync so there is probably a better place to test this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@sanych-sun sanych-sun requested a review from rstam May 23, 2023 16:50
Copy link
Contributor

@rstam rstam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is still one call to:

new FindExpressionProjectionDefinition

in IFindFluentExtensions.cs.

I think it can be replaced with a call to:

new ExpressionProjectionDefinition

That would mean that the FindExpressionProjectionDefinition class is no longer used at all (and that's a good thing).

We can't really remove the FindExpressionProjectionDefinition class because it is public and we don't know if any users is creating it directly.

{
public string FirstName;
public string FamilyName;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is no longer used.

Did you not find a new (better) place for the test you removed in this commit? (The test that used this class)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the unused class. Will create tests once we decide how to share that useful helper methods.

}

internal override RenderedProjectionDefinition<TOutput> RenderForFind(IBsonSerializer<TInput> sourceSerializer, IBsonSerializerRegistry serializerRegistry,
LinqProvider linqProvider)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: for better readability I prefer that parameters either all be on one line or each on a separate line, as in:

internal override RenderedProjectionDefinition<TOutput> RenderForFind(
    IBsonSerializer<TInput> sourceSerializer,
    IBsonSerializerRegistry serializerRegistry,
    LinqProvider linqProvider)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done


internal virtual RenderedProjectionDefinition<TProjection> RenderForFind(IBsonSerializer<TSource> sourceSerializer, IBsonSerializerRegistry serializerRegistry, LinqProvider linqProvider)
=> Render(sourceSerializer, serializerRegistry, linqProvider);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a really good suggestion to make this method virtual!

We also discussed overriding this method in subclasses where it doesn't make sense and throw InvalidOperationException.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@sanych-sun sanych-sun requested a review from rstam May 24, 2023 19:39
Copy link
Contributor

@rstam rstam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@sanych-sun sanych-sun merged commit 2cf3d6c into mongodb:master May 24, 2023
@sanych-sun sanych-sun deleted the csharp4656 branch May 24, 2023 21:13
dnickless pushed a commit to dnickless/mongo-csharp-driver that referenced this pull request Aug 24, 2023
@connorstorer-kbxcom
Copy link

connorstorer-kbxcom commented Nov 17, 2023

There is still one call to:

new FindExpressionProjectionDefinition

in IFindFluentExtensions.cs.

I think it can be replaced with a call to:

new ExpressionProjectionDefinition

That would mean that the FindExpressionProjectionDefinition class is no longer used at all (and that's a good thing).

We can't really remove the FindExpressionProjectionDefinition class because it is public and we don't know if any users is creating it directly.

@rstam @sanych-sun Prior to 2.19.2, (2.19.1) this was valid below, where Projection = Builders<TDocumentEntity>.Projection.Expression returned public FindExpressionProjectionDefinition but now returns sealed ExpressionProjectionDefinition. What's your suggestion on how I can continue to Cast/Select/Project this. This is used within a test suite and I know there are a lot of missing pieces below, but I wanted to get your shot from the hip opinion? Much appreciated, no matter how brutal it is :D! This does seem to be addressed in one case #1146

public void ApplyProjections<TDocumentEntity, TSelectEntity>()
{
    Expression<Func<TDocumentEntity, TSelectEntity>> select = null;
    var findOpts = new FindOptions<TDocumentEntity, TSelectEntity>
    {
        Projection = Builders<TDocumentEntity>.Projection.Expression(select)
    };
    var projectedData = GetData<TDocumentEntity, TSelectEntity, TSelectEntity>(findOpts.Projection);
}

public async Task<List<TReturnEntity>> GetData<TDocumentEntity, TSelectEntity, TReturnEntity>(
    ProjectionDefinition<TDocumentEntity, TSelectEntity> select)
{
    var queryable = _instance.CollectionDocuments.AsQueryable();
    var additionalExp = GetSomeExpression();
    queryable = queryable.Where(additionalExp);
    var expSelect = select as FindExpressionProjectionDefinition<TDocumentEntity, TSelectEntity>;
    return queryable.Select(expSelect.Expression).Cast<TReturnEntity>().ToList();
}

@connorstorer-kbxcom
Copy link

Bypassing with some temporary reflection as noted in CSHARP-4860.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants