[CSHARP-1309] IndexKeysDefinitionBuilder should support defining multikey indexes on members of an array of embedded documents Created: 11/Jun/15  Updated: 21/Jul/22

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

Type: New Feature Priority: Major - P3
Reporter: Craig Wilson Assignee: Unassigned
Resolution: Unresolved Votes: 10
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Issue Links:
Depends
depends on CSHARP-1955 Add a type safe way to use the IndexK... Backlog
Duplicate
is duplicated by CSHARP-1340 Provide a type-safe way to create mul... Closed

 Description   

Given the following classes:

public class User
{
    public Object Id { get; set; }
    public IEnumerable<Login> Logins { get; set; }
    // etc...
}
 
public class Login
{
    public string LoginProvider { get; set; }
    public string ProviderKey { get; set; }
    // etc...
}

There should be a type-safe way to create multikey indexes on members of the embedded Login documents. One possible way to express that could be:

var indexKeysDefinition = Builders<User>.IndexKeys
    .Ascending(user => user.Logins.Select(login => login.LoginProvider))
    .Ascending(user => user.Logins.Select(login => login.ProviderKey));

Workaround

Use strings instead.

var indexKeysDefinition = Builders<User>.IndexKeys
    .Ascending("Logins.LoginProvider")
    .Ascending("Logins.ProviderKey");



 Comments   
Comment by Tyler Ohlsen [ 21/Aug/19 ]

This is also uniquely a problem with dictionary items. 

We use the dictionary representation of `ArrayOfDocuments` and we need an index on the dictionary keys. Therefore, the index I want is: { "MyDictionary.k": 1 }, but there's currently no way to represent that in a type-safe way.  Queries are able to build the correct breadcrumb property name (with 'k') by a type-safe expression, so I'm surprised that index definitions are not able to do the same.

Comment by Alexander Regueiro [ 25/Sep/17 ]

Any progress on this lately?

Comment by Craig Wilson [ 08/Apr/16 ]

It would certainly go onto the existing IndexKeysDefinitionBuilder<T> class and then also live as an extension method on the IndexKeysDefinition<T> class. You can see the existing implementations here:

https://github.com/mongodb/mongo-csharp-driver/blob/master/src/MongoDB.Driver/IndexKeysDefinitionBuilder.cs#L249

Comment by Kenneth Siewers M?ller [ 08/Apr/16 ]

Thanks! Well, I actually didn't spend a whole lot of time on the code, but I will definitely look into your proposal.
Since I just needed a solution for my own project I wouldn't go through the creation of a new class, but if I am going to build the feature into the driver itself it makes sense
Are you thinking about extending the builder or should I stick with an extension method?
I like the extension method approach, but it makes unit testing almost impossible.

Comment by Craig Wilson [ 08/Apr/16 ]

Hi Kenneth,

Couple of things. First, submitting a pull request will let us get your name in the commit history, which is nice for you and a thanks for helping us out.

Second, this looks like a good start, but isn't there yet. You're calling the Render method very early in the process, which means that you are having to us the global BsonSerializer.LookupSerializer method. This defeats the purpose of the XXXDefinition classes. What you are going to want to do create a class that inherits from IndexKeysDefinition<T> and return that from these methods. This class contains a Render method you'll override and be provided both with a serializer and a serializerRegistry to use.

Overall, I think this looks like a good solution. Great job.
Craig

Comment by Kenneth Siewers M?ller [ 08/Apr/16 ]

I have created two extension methods, which works on strongly typed collections:

public static IndexKeysDefinition<T> Ascending<T, TChild>(this IndexKeysDefinitionBuilder<T> builder, Expression<Func<T, IEnumerable<TChild>>> collectionExpression, Expression<Func<TChild, object>> memberExpression)
{
    var fieldDefinition = CreateFieldDefinition(collectionExpression, memberExpression);
    return builder.Ascending(fieldDefinition);
}
 
public static IndexKeysDefinition<T> Descending<T, TChild>(this IndexKeysDefinitionBuilder<T> builder, Expression<Func<T, IEnumerable<TChild>>> collectionExpression, Expression<Func<TChild, object>> memberExpression)
{
    var fieldDefinition = CreateFieldDefinition(collectionExpression, memberExpression);
    return builder.Descending(fieldDefinition);
}
 
private static StringFieldDefinition<T> CreateFieldDefinition<T, TChild>(Expression<Func<T, IEnumerable<TChild>>> collectionExpression, Expression<Func<TChild, object>> memberExpression)
{
    var collection = new ExpressionFieldDefinition<T>(collectionExpression);
    var child = new ExpressionFieldDefinition<TChild>(memberExpression);
    var collectionDefinition = collection.Render(BsonSerializer.LookupSerializer<T>(), BsonSerializer.SerializerRegistry);
    var childDefinition = child.Render(BsonSerializer.LookupSerializer<TChild>(), BsonSerializer.SerializerRegistry);
            
    var fieldDefinition = new StringFieldDefinition<T>(collectionDefinition.FieldName + "." + childDefinition.FieldName);
    return fieldDefinition;
}

Usage:

collection.Indexes.CreateOne(Builders<SomeClass>.IndexKeys.Ascending(s => s.ListOfChildren, m => m.ChildProperty), new CreateIndexOptions { Unique = true });

I know it's not working on nested arrays etc. but it does generate the proper indexes.

There might also be a much easier way to create the property names, but I haven't found it.

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