-
Notifications
You must be signed in to change notification settings - Fork 1.3k
CSHARP-4656 Simplify A : "$A" to A : 1 only on find #1087
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
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); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, fixed
There was a problem hiding this 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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add copyright
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add copyright
There was a problem hiding this comment.
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 }"); |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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] |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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();
}
There was a problem hiding this comment.
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>( |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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; } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this 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.
src/MongoDB.Driver/FindFluent.cs
Outdated
{ | ||
projection = new FindExpressionProjectionDefinition<TDocument, TNewProjection>(expressionProjection.Expression); | ||
} | ||
|
There was a problem hiding this comment.
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);
}
There was a problem hiding this comment.
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).
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this 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; | ||
} |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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)
There was a problem hiding this comment.
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); | ||
|
There was a problem hiding this comment.
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
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM
@rstam @sanych-sun Prior to 2.19.2, (2.19.1) this was valid below, where 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();
} |
Bypassing with some temporary reflection as noted in CSHARP-4860. |
No description provided.