5

This question is quite on the same principles as this one but I'd like to create an index using strongly typed approach on an object property when this object is nested in an array of the collection.

I can use:

new CreateIndexModel<T>( Builders<T>.IndexKeys.Ascending( a ) )

where a is an Expression which accesses to a direct property.

But I've found nothing similar to:

Builders<Library>.Filter.ElemMatch(x => x.Author.Books, b => b.IsVerified == false));

so that I can define as index some field of an object nested in an array which is a member of the collection.

Is that possible to do it and how?

2 Answers 2

5

Consider the following data model:

public class Course
{
   public string Name { get; set; }
   public string Teacher { get; set; }
}

public class Student
{
  public string Name { get; set; }
  public int Age { get; set; }
  public ReadOnlyCollection<Course> Courses { get; set; }
}

You can create an ascending multikey index on the field Courses in the following manner:

using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ConsoleApp1
{
  public static class Program
  {
    private static MongoClient Client;
    private static IMongoDatabase Database;
    private static IMongoCollection<Student> Collection;

    public static async Task Main(string[] args)
    {
      Client = new MongoClient();
      Database = Client.GetDatabase("test-index");
      Collection = Database.GetCollection<Student>("students");

      var courses1 = new List<Course>()
      {
        new Course { Name = "Math", Teacher = "Bob" }
      }.AsReadOnly();

      var courses2 = new List<Course>()
      {
        new Course { Name = "Computer Science", Teacher = "Alice" }
      }.AsReadOnly();

      var mark = new Student
      {
        Name = "Mark",
        Courses = courses1,
        Age = 20
      };

      var lucas = new Student
      {
        Name = "Lucas",
        Courses = courses2,
        Age = 22
      };

      await Collection.InsertManyAsync(new[] { mark, lucas }).ConfigureAwait(false);


      var model = new CreateIndexModel<Student>(
        Builders<Student>.IndexKeys.Ascending(s => s.Courses));

      await Collection.Indexes.CreateOneAsync(model).ConfigureAwait(false);

      Console.WriteLine("All done !");
    }
  }
}

This query is served by the index you created: db.students.find({Courses: {"Name": "Math", "Teacher": "Bob"}})

If you don't want to create an index on the entire array Courses, but instead you want an index on the field Name of the nested object (the Course object), this is the way to go:

using MongoDB.Driver;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace ConsoleApp1
{
  public static class Program
  {
    private static MongoClient Client;
    private static IMongoDatabase Database;
    private static IMongoCollection<Student> Collection;

    public static async Task Main(string[] args)
    {
      Client = new MongoClient();
      Database = Client.GetDatabase("test-index");
      Collection = Database.GetCollection<Student>("students");

      var courses1 = new List<Course>()
      {
        new Course { Name = "Math", Teacher = "Bob" }
      }.AsReadOnly();

      var courses2 = new List<Course>()
      {
        new Course { Name = "Computer Science", Teacher = "Alice" }
      }.AsReadOnly();

      var mark = new Student
      {
        Name = "Mark",
        Courses = courses1,
        Age = 20
      };

      var lucas = new Student
      {
        Name = "Lucas",
        Courses = courses2,
        Age = 22
      };

      await Collection.InsertManyAsync(new[] { mark, lucas }).ConfigureAwait(false);


      var model = new CreateIndexModel<Student>(
        Builders<Student>.IndexKeys.Ascending("Courses.Name"));

      await Collection.Indexes.CreateOneAsync(model).ConfigureAwait(false);

      Console.WriteLine("All done !");
    }
  }
}

This query is served by the index you created: db.students.explain("executionStats").find({"Courses.Name": "Math"})

A possible way to avoid the usage of magic strings in my second example is exploiting the power of the nameof C# operator: $"{nameof(Student.Courses)}.{nameof(Course.Name)}"

Sign up to request clarification or add additional context in comments.

4 Comments

Thanks for this detailed answer. But it tends to confirm we must define the index using a string ("Courses.Name") and not a strongly typed expression based definition, isn't it ?
Hi @lemon I think this issue on Mongodb's jira confirm that, at the moment, there is no support for type safety.
@lemon see my comment above for a possible way to mitigate the issue
OK, thanks a lot, I'll use your string approach while the issue you mentioned is not solved.
3

here's the strongly typed way to created indexes for nested fields using the MongoDB.Entities convenience library. [disclaimer: i'm the author]

using MongoDB.Entities;
using System.Collections.Generic;

namespace StackOverflow
{
    public class Program
    {
        public class Parent : Entity
        {
            public Child[] Children { get; set; }
        }

        public class Child
        {
            public List<Friend> Friends { get; set; }
        }

        public class Friend
        {
            public string Name { get; set; }
        }

        static void Main(string[] args)
        {
            new DB("test");

            DB.Index<Parent>()
              .Key(p => p.Children[-1].Friends[-1].Name, KeyType.Ascending)
              .Create();

        }
    }
}

the above creates an ascending index on the name field which is nested two levels deep with the following command:

db.Parent.createIndex({
    "Children.Friends.Name": NumberInt("1")
}, {
    name: "Children.Friends.Name(Asc)",
    background: true
})

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.