Search code examples
c#mongodbmongodb.driver

MongoDb / C# filter and get all subdocuments


I'm having difficulties querying a Mongo-DB collection.

The document I'm using

public class Customer
{
    public ObjectId Id { get; set; }

    public int CustomerId { get; set; }

    public List<Address> Addresses { get; set; }
}

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

Some sample-Data

{
    CustomerId: 2,
    Addresses: [
        {
            Name: "Daniel"
        },
        {
            Name: "Eric"
        },
        {
            Name: "Dan"
        }
        ]
}

I now want to query the documents by the CustomerId and filter some of the Addresses-values to return all Addresses with a Name like %Dan%. As the address-collection can be huge I want to already filter those addresses at query-time

var mdb = mongoClient.GetDatabase("test");
var collection = mdb.GetCollection<WebApi>("Customer");
var builder = Builders<Customer>.Filter;
var searchTerm = "Dan";

When querying like that it works but it holds all addresses:

var filter = builder.And(builder.Eq("InstallationId", 2),
                         builder.Regex("Addresses.Name", new BsonRegularExpression(".*" + searchTerm + ".*", "i")))
var result = collection.Find(filter).FirstOrDefault();

What I'd like to get is:

[
    {
        Name: "Daniel"
    },
    {
        Name: "Dan"
    }
]

or

{
    CustomerId: 2,
    Addresses: [
        {
            Name: "Daniel"
        },
        {
            Name: "Dan"
        }
        ]
}

I also tried several approaches with Aggregate/Match/Project but can't get it to work. "AsQueryable" also does not work as IndexOf is not implemented on the driver.

collection.AsQueryable().Where(x => x.CustomId == 2).SelectMany(x =>
                x.Addresses.Where(a => a.Name.IndexOf(searchTerm, StringComparison.InvariantCultureIgnoreCase) >= 0).ToList();

Used Versions:

  • MongoDB.Driver v 2.6.1 (also Bson)
  • .Net Framework 4.5.2

Solution

  • This should get you going:

    var result = collection
        .Aggregate()
        .Match(c => c.CustomerId == 2)
        .Project(c => new
            {
                c.CustomerId,
                Addresses = c.Addresses.Where(a => a.Name.IndexOf(searchTerm) != -1)
            })
        .ToList();
    

    The driver will translate this into:

    db.Customer.aggregate([{
        $match: { CustomerId: 2 }
    }, {
        $project: {
            CustomerId: "$CustomerId",
            Addresses: {
                $filter: {
                    input: "$Addresses",
                    as: "a",
                    cond: {
                        $ne: [ { $indexOfBytes: [ "$$a.Name", "Dan" ] }, -1 ]
                    }
                }
            },
            _id: 0
        }
    }])
    

    For the case-insensitive version, there will be a the option to use $regex at some stage in the future (see https://jira.mongodb.org/browse/SERVER-11947). However, what you can do just now is use ToUpper():

    var result = collection
        .Aggregate()
        .Match(c => c.CustomerId == 2)
        .Project(c => new
            {
                c.CustomerId,
                Addresses = c.Addresses.Where(a => a.Name.ToUpper().IndexOf(searchTerm.ToUpper()) != -1)
            })
        .ToList();