Получить все элементы, где subDocument.value в listOfStrings

Я использую пакет nuget MongoDB.Driver в dotnetcore 2.1. Я пытаюсь вернуть список документов в коллекции, где поле вложенного документа равно любым элементам, содержащимся в моем списке. В идеале мне это нужно в синтаксисе C# для пакета nuget MongoDB.Driver для dotnetcore 2.1.

Документ { "_id" : "e29628a65e914c1e91b3fd9cbf6f2353", "Enabled" : true, "Name" : "Document123", "DisplayName" : "Some Document", "Description" : "Some Description", "Data" : [ "lastname", "email", "firstname", "zipcode" ], "Items" : [ { "_id" : "1", "Name" : "bob" }, { "_id" : "2", "Name" : "smith" } ] }

Если бы это был SQL, вот что я пытаюсь сделать:

SELECT * FROM Document a, Item b WHERE a.Id = b.DocumentId AND b.Name IN ('bob', 'smith', 'alex')

Вот что у нас не работает с драйвером MongoDB:

string[] names = new [] { "bob", "smith", "alex" };
var document = new BsonDocument()
{
                new BsonElement("Items.Name", new BsonDocument()
                {
                    new BsonElement("$in", new BsonArray(names))
                })
            };
var itemsQuery = collection
                .Aggregate()
                .Match(document)
                ;

            var items = itemsQuery.ToList();

Заранее спасибо.

2 ответа

Решение

Оказывается, после этого нам пришлось "раскрутиться" и выполнить запрос на совпадение. Вот код, который работал для нас.

        var collection = GetCollection<Document>();

        var document = new BsonDocument()
        {
            new BsonElement("Name", "Document123"),
            new BsonElement("Items.Name", new BsonDocument()
            {
                new BsonElement("$in", new BsonArray(new [] { "bob", "smith", "alex"}))
            })
        };

        var itemsQuery = collection
            .Aggregate()
            .Unwind(d => d.Items)
            .Match(document)
            ;

        var items = itemsQuery.ToList();

Два начальных замечания:

а) Ваш запрос должен включать $match этап в начале, чтобы ускорить вещи. Это может показаться странным $match в два раза, но с большим количеством документов вы увидите существенную разницу, потому что у вас будет гораздо меньше документов в $unwind этап.

б) Вы можете написать строковый запрос с меньшим количеством кода, например:

var itemsQuery = collection
    .Aggregate()
    .Unwind(document => document.Items)
    .Match("{ 'Name' : 'Document123', 'Items.Name' : { $in: [ '" + string.Join("', '", names) + "' ] } }");

Но вы можете написать все это в некоторой типобезопасной версии (например, "не используя строки или BsonDocument") как это:

/// <summary>
/// This would be just to avoid having to create two almost identical types which, however, could be done as well if you don't like abstract base types
/// </summary>
public abstract class DocumentBase
{
    public string Id { get; set; }
    public string Name { get; set; }
    // this is where all other fields would go like "Enabled", "DisplayName", etc...
}

/// <summary>
/// Represents the <see cref="DocumentBase"/> type but with an unwound <see cref="Items"/> property which is actually a single <see cref="Item"/>,
/// </summary>
public class UnwoundDocument : DocumentBase
{
    public Item Items { get; set; }
}

/// <summary>
/// This is the real "Document" type with a <see cref="List{Item}"/> property called <see cref="Items"/>.
/// </summary>
public class Document : DocumentBase
{
    public List<Item> Items { get; set; }
}

/// <summary>
/// This would hold all properties of an Item - I've dropped the "Id" property since it's not needed for this sample
/// </summary>
public class Item
{
    public string Name { get; set; }
}

С этими типами вы можете запустить следующий код:

public class Program
{
    static void Main(string[] args)
    {
        var collection = new MongoClient().GetDatabase("just_a_test").GetCollection<Document>("Document");
        // clear out all existing documents to allow for multiple runs of this sample app
        collection.DeleteMany(FilterDefinition<Document>.Empty);

        // insert our test document
        collection.InsertOne(new Document { Id = "some id", Name = "Document123", Items = new List<Item> { new Item { Name = "bob" }, new Item { Name = "smith" } } });

        // create a bunch of filters
        var namesToSearchFor = new List<string> { "bob", "smith", "alex" };
        Expression<Func<Item, bool>> itemFilter = item => namesToSearchFor.Contains(item.Name);
        FilterDefinitionBuilder<Document> fdb = Builders<Document>.Filter;
        FilterDefinition<Document> documentFilter = fdb.Eq(f => f.Name, "Document123") & fdb.ElemMatch(f => f.Items, itemFilter);

        // construct the aggregation pipeline
        IAggregateFluent<UnwoundDocument> pipeline = collection
            .Aggregate()
            .Match(documentFilter) // filter first for performance reasons (technically not needed)
            .Unwind(document => document.Items) // flatten the "Items" array
            .As(BsonSerializer.SerializerRegistry.GetSerializer<UnwoundDocument>()) // this is to tell the driver that the resulting document will look like our "UnwoundDocument" type
            .Match(d => namesToSearchFor.Contains(d.Items.Name)); // just so we can filter again and use the result in a nicely type-safe manner

        // just print out all results for demo purposes
        foreach (var result in pipeline.ToList())
        {
            Console.WriteLine(result.ToJson());
        }
    }
}
Другие вопросы по тегам