Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Proof of concept for supporting compound primary keys in LINQ with the EF Core Provider. #1161

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

Closed
rstam wants to merge 2 commits into mongodb:master from rstam:efcorecompoundprimarykey

Conversation

@rstam
Copy link
Contributor

@rstam rstam commented Aug 16, 2023

No description provided.

/// <param name="serializer">The serializer.</param>
/// <param name="nominalType">The nominal type.</param>
/// <returns>A BsonSerializationInfo.</returns>
public static BsonSerializationInfo CreateWithPath(IEnumerable<string> elementPath, IBsonSerializer serializer, Type nominalType)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Adding a new public constructor was a breaking change so I added a new static factory method instead.

Note: a new public constructor resulted in compile time ambiguity when called with null as the first argument.

sanych-sun reacted with thumbs up emoji
public string ElementName
{
get { return _elementName; }
get { return _elementPath?.Single(); }
Copy link
Contributor Author

Choose a reason for hiding this comment

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

One advantage of using Single here is that whenever LINQ2 calls this property it will throw an exception if the member is grouped under a nested path. That's far better than silently doing the wrong thing.

sanych-sun reacted with thumbs up emoji
Copy link
Member

@sanych-sun sanych-sun Aug 16, 2023

Choose a reason for hiding this comment

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

Agree with the fact that we want to throw here. But I think we have to throw something more readable rather that just "Sequence contains more then one element".

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fair enough. We're at POC stage right now.

/// </summary>
/// <param name="newSerializationInfo">The new info.</param>
/// <returns>A new BsonSerializationInfo.</returns>
[Obsolete("This method is no longer relevant because field names are now allowed to contain dots.")]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

We never call this method.

Should we delete it? Technically that's a breaking change, but the method no longer works reliably anyway since the server now allows . in element names.

Copy link
Contributor

@JamesKovacs JamesKovacs Aug 16, 2023

Choose a reason for hiding this comment

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

Given that it is a breaking change, marking it as [Obsolete] is the right way forward IMHO.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This method was "broken" long before this PR.

We simply didn't realize it because we never call it.

Would it be OK to just delete it?

Copy link
Contributor

@JamesKovacs JamesKovacs Aug 16, 2023

Choose a reason for hiding this comment

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

While I agree that the inclusion of . in element names breaks this method, that is not a mainline use case. This method works for the common case of element names without .. We can make the breaking change in 3.0.0, but not in a 2.X release IMHO.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not proposing we make any changes to this method.

I'm merely pointing out that it's already broken, and that we don't use it.

Although I do wish we could just delete it.


// constructors
public MemberSerializationInfo(stringelementName, IBsonSerializer serializer)
public MemberSerializationInfo(IReadOnlyList<string>elementPath, IBsonSerializer serializer)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is an internal class so this is not a publicly breaking change.

JamesKovacs reacted with thumbs up emoji
{
subField = AstExpression.GetField(subField, subFieldName);
}
return new AggregationExpression(expression, subField, serializationInfo.Serializer);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is a relatively simple change. We just need to call GetField more than once when there is a path.

If the path list has count == 1 this code is equivalent to the previous code.

{
subField = subField.SubField(subFieldName, subFieldSerializer);
}
return subField;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Same as above but this time for filters.

I'm surprised I only found TWO places that needed to be changed. Hope I didn't miss any.

Copy link
Member

@sanych-sun sanych-sun Aug 16, 2023

Choose a reason for hiding this comment

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

What about:
SelectMethodToFilterFieldTranslator line 47

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks. I missed that one.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think we can ignore this one for now.

If it ever gets used when a path is involved the call to ElementName will throw an exception and we can deal with it then.

sanych-sun reacted with thumbs up emoji
[Fact]
public void Deserialize_should_ungroup_members()
{
var serializedOrderItem = "{ _id : { OrderId : 1, ProductId : 2 }, Quantity : 3 }";
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that OrderId and ProductId are grouped under the _id element.

else
{
var stages = Translate(collection, queryable);
AssertStages(stages, "{ $project : { _v : '$_id.OrderId', _id : 0 } }");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Note that the use of $_id.OrderId instead of $OrderId.

public int Quantity { get; set; }
}

private class OrderItemsSerializer : ClassSerializerBase<OrderItem>, IBsonDocumentSerializer
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is for testing.

We would implement similar code in the EntitySerializer.

private class OrderItemsSerializer : ClassSerializerBase<OrderItem>, IBsonDocumentSerializer
{
// public methods
public bool IsGroupedMember(string name, out string groupElementName)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Oops this method is obsolete. Ignore it. It was when I was experimenting with a new interface instead.

private string_elementName;
private IBsonSerializer _serializer;
private Type _nominalType;
private readonlyIReadOnlyList<string>_elementPath;
Copy link
Member

@sanych-sun sanych-sun Aug 16, 2023

Choose a reason for hiding this comment

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

I would suggest to have both: _elementPath and _elementName. We can use _elementName for situation when there is a single element path. And switch to the _elementPath in that rare case when there are multiple. It could save some allocations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I understand that there is an additional allocation.

I was prioritizing simplicity and correctness over trying to optimize away an allocation.

The logic is that an ElementName is the same thing as an ElementPath with a single entry.

What I didn't want to do was force every user of this class to first test whether ElementName or ElementPath should be used. At that point we might as well use the interface approach and test whether the interface is implemented.

Do you think we can eliminate the allocation without complicating how this class can be consumed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess a related question is: do we need a variable length path at all?

Is it sufficient to only support a single level of nesting?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've pushed a commit that has mutually exclusive _elementName and _elementPath to reduce allocations.

Note the changes in the users of this class that now have to check whether ElementPath is null or not and only use ElementName when there is no ElementPath.

public string ElementName
{
get { return _elementName; }
get { return _elementPath?.Single(); }
Copy link
Member

@sanych-sun sanych-sun Aug 16, 2023

Choose a reason for hiding this comment

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

Agree with the fact that we want to throw here. But I think we have to throw something more readable rather that just "Sequence contains more then one element".

{
subField = subField.SubField(subFieldName, subFieldSerializer);
}
return subField;
Copy link
Member

@sanych-sun sanych-sun Aug 16, 2023

Choose a reason for hiding this comment

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

What about:
SelectMethodToFilterFieldTranslator line 47

Copy link
Contributor

@JamesKovacs JamesKovacs left a comment

Choose a reason for hiding this comment

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

Straightforward and reasonable approach. Minor comments inline.

/// <summary>
/// Gets element path.
/// </summary>
public IReadOnlyList<string> ElementPath
Copy link
Contributor

@JamesKovacs JamesKovacs Aug 16, 2023

Choose a reason for hiding this comment

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

Given this is a collection of paths, shouldn't this be ElementPaths?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't think so.

There is only ONE "path". The one path is composed of a series of "element names".

So path is not plural.

Another way to say it: this is not a collection of paths. A path is a collection of element names.

Copy link
Contributor

@JamesKovacs JamesKovacs Aug 16, 2023

Choose a reason for hiding this comment

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

OK.

/// </summary>
/// <param name="newSerializationInfo">The new info.</param>
/// <returns>A new BsonSerializationInfo.</returns>
[Obsolete("This method is no longer relevant because field names are now allowed to contain dots.")]
Copy link
Contributor

@JamesKovacs JamesKovacs Aug 16, 2023

Choose a reason for hiding this comment

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

Given that it is a breaking change, marking it as [Obsolete] is the right way forward IMHO.

return collection;
}

[BsonSerializer(typeof(OrderItemsSerializer))]
Copy link
Contributor

@JamesKovacs JamesKovacs Aug 16, 2023

Choose a reason for hiding this comment

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

So it is the responsibility of OrderItemsSerializer to configure the composite _id. If we agree on this path forward, it would be useful to allow specification of multiple [BsonId(Order=N)] attributes as well as BsonClassMap<T> configuration.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wasn't expecting to add any support to BsonClassMapSerializer for this.

I was thinking this would only be used by the EF Core Provider.

However, now that you ask about it, if we want applications written using the C# driver to interoperate with data written by the EF Core Provider I suppose we have to add support for configuring that kind of serialization in the driver also.

Copy link
Contributor

@JamesKovacs JamesKovacs Aug 16, 2023

Choose a reason for hiding this comment

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

I agree that the most common use case is for EF Core, but it would be surprising for EF Core to write data that the driver cannot interoperate with.

Copy link
Contributor

@JamesKovacs JamesKovacs left a comment

Choose a reason for hiding this comment

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

See comments inline.

/// </summary>
/// <param name="newSerializationInfo">The new info.</param>
/// <returns>A new BsonSerializationInfo.</returns>
[Obsolete("This method is no longer relevant because field names are now allowed to contain dots.")]
Copy link
Contributor

@JamesKovacs JamesKovacs Aug 16, 2023

Choose a reason for hiding this comment

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

While I agree that the inclusion of . in element names breaks this method, that is not a mainline use case. This method works for the common case of element names without .. We can make the breaking change in 3.0.0, but not in a 2.X release IMHO.

/// <summary>
/// Gets element path.
/// </summary>
public IReadOnlyList<string> ElementPath
Copy link
Contributor

@JamesKovacs JamesKovacs Aug 16, 2023

Choose a reason for hiding this comment

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

OK.

return collection;
}

[BsonSerializer(typeof(OrderItemsSerializer))]
Copy link
Contributor

@JamesKovacs JamesKovacs Aug 16, 2023

Choose a reason for hiding this comment

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

I agree that the most common use case is for EF Core, but it would be surprising for EF Core to write data that the driver cannot interoperate with.

Copy link
Contributor Author

rstam commented Aug 17, 2023

Moving to a new PR now that we have a JIRA ticket number:

#1161

@rstam rstam closed this Aug 17, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@JamesKovacs JamesKovacs Awaiting requested review from JamesKovacs

@sanych-sun sanych-sun Awaiting requested review from sanych-sun

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

AltStyle によって変換されたページ (->オリジナル) /