【问题标题】:Get all items where subDocument.value in listOfStrings获取 listOfStrings 中 subDocument.value 所在的所有项目
【发布时间】:2019-01-10 18:51:24
【问题描述】:

我在 dotnetcore 2.1 中使用 MongoDB.Driver nuget 包。我正在尝试返回集合中的文档列表,其中子文档字段等于我拥有的列表中包含的任何项目。理想情况下,我需要 dotnetcore 2.1 的 MongoDB.Driver nuget 包的 C# 语法。

文档 { "_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();

提前致谢。

【问题讨论】:

  • stackoverflow.com/questions/3985214/… 的可能欺骗。在你的情况下使用db.Document.aggregate([ {$match: {'Items.Name': {$in:['bob', 'smith', 'alex']}}}, {$project: { Items: {$filter: { input: '$Items', cond: {$in: ['$$this.Name', ['bob', 'smith', 'alex']]} }} }} ])
  • 我评论中的查询对您不起作用吗?运行您拥有的 c# 查询时遇到什么问题?
  • 是的,很遗憾,我们使用我们的驱动程序时它并不适合我们。我们必须展开,然后以这种方式基于子文档进行查询。

标签: mongodb c#-4.0 mongodb-query mongodb.driver


【解决方案1】:

事实证明,在此之后我们必须“放松”并运行匹配查询。这是对我们有用的代码。

        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();

【讨论】:

    【解决方案2】:

    两个开头的备注:

    a) 您的查询应在开头包含$match 阶段以加快处理速度。两次$match 可能会让人感觉很奇怪,但是对于大量文档,您会看到很大的不同,因为您最终在$unwind 阶段获得的文档要少得多。

    b) 您可以使用更少的代码编写基于字符串的查询,如下所示:

    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());
            }
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-11-02
      相关资源
      最近更新 更多