【问题标题】:How to access deeply nested array with MongoDB (ASP.NET Core 2.2)如何使用 MongoDB (ASP.NET Core 2.2) 访问深度嵌套的数组
【发布时间】:2019-06-27 13:23:30
【问题描述】:

我正在使用 MongoDB 设计一个库存管理系统。我有以下数据库结构:

inventory
└─storage_slots
└─storage_locations
...etc...

每次添加新的Slot 时,都会将表示插槽在层次结构中的位置的树添加到storage_locations 集合中以表示其位置(根据位置、房间、部分、架子)。到目前为止,我已经成功地添加了一个尚未使用任何位置字段的新项目:(该插槽也已添加到 storage_slots 集合中)

{
"_id" : ObjectId("5c57169f0863d665c7f13d27"),
"CreatedUtc" : {
    "$date" : 1549211298017
},
"UpdatedUtc" : {
    "$date" : 1549211298017
},
"Description" : null,
"Address" : null,
"StorageRooms" : [
    {
        "_id" : ObjectId("5c57169f0863d665c7f13d28"),
        "CreatedUtc" : {
            "$date" : 1549211297719
        },
        "UpdatedUtc" : {
            "$date" : 1549211297719
        },
        "Description" : null,
        "StorageSections" : [
            {
                "_id" : ObjectId("5c57169f0863d665c7f13d29"),
                "CreatedUtc" : {
                    "$date" : 1549211297719
                },
                "UpdatedUtc" : {
                    "$date" : 1549211297719
                },
                "Description" : null,
                "StorageShelves" : [
                    {
                        "_id" : ObjectId("5c57169f0863d665c7f13d2a"),
                        "CreatedUtc" : {
                            "$date" : 1549211297719
                        },
                        "UpdatedUtc" : {
                            "$date" : 1549211297719
                        },
                        "Description" : null,
                        "StorageSlotIds" : [
                            ObjectId("5c57169f0863d665c7f13d26")
                        ]
                    }
                ]
            }
        ]
    }
]
}

需要明确的是,storage_locations 是上述层次结构,而storage_slots 只是插槽的集合。

但是,如果层次结构中已经存在这些字段,则运行以下代码:(我从this 帖子中获取灵感)

var filter = Builders<StorageLocation>.Filter.And(
            Builders<StorageLocation>.Filter.Where(location => location.Id == id),
            Builders<StorageLocation>.Filter.Eq("StorageRooms.Id", roomId),
            Builders<StorageLocation>.Filter.Eq("StorageRooms.$.StorageSections.Id", sectionId),
            Builders<StorageLocation>.Filter.Eq("StorageRooms.$.StorageSections.$.StorageShelves.Id", shelfId));
        var update =
            Builders<StorageLocation>.Update.Push("StorageRooms.$.StorageSections.$.StorageShelves.$.StorageSlotIds",
                storageSlotIds);
        return await UpdateAsync(filter, update, cancellationToken);

另外,如果只定义了其中一些,那么我会混合使用这两种方法,但我决定不在这里展示,因为它们建立在相同的原则之上,不会对问题产生影响。

问题

只要上面的代码运行。我收到以下错误:

InvalidCastException: Unable to cast object of type 'MongoDB.Bson.ObjectId[]' to type 'MongoDB.Bson.ObjectId'.

MongoDB.Bson.Serialization.Serializers.SerializerBase<TValue>.MongoDB.Bson.Serialization.IBsonSerializer.Serialize(BsonSerializationContext context, BsonSerializationArgs args, object value)

//annoying scrollbar

错误发生在这一行:

return await UpdateAsync(filter, update, cancellationToken);

方法是:

public Task<UpdateResult> UpdateAsync(FilterDefinition<T> filter, UpdateDefinition<T> updateDefinition,
        string database, string collection, CancellationToken cancellationToken)
    {
        return _mongoContext.MongoClient.GetDatabase(database).GetCollection<T>(collection)
            .UpdateOneAsync(filter, updateDefinition.Set(o => o.UpdatedUtc, DateTime.UtcNow),
                cancellationToken: cancellationToken);
    }

额外的东西

这里有一些与问题更相关的类:

public class StorageLocation : Dbo
{
    public string Description { get; set; }
    public Address Address { get; set; }
    public IEnumerable<StorageRoom> StorageRooms { get; set; }
}
public class StorageRoom : Dbo
{
    public string Description { get; set; }
    public IEnumerable<StorageSection> StorageSections { get; set; }
}
public class StorageSection : Dbo
{
    public string Description { get; set; }
    public IEnumerable<StorageShelf> StorageShelves { get; set; }
}
public class StorageShelf : Dbo
{
    public string Description { get; set; }
    public IEnumerable<ObjectId> StorageSlotIds { get; set; }
}
public class StorageSlot : Dbo
{
    public string Description { get; set; }

    public ObjectId LocationId { get; set; }
    public ObjectId RoomId { get; set; }
    public ObjectId SectionId { get; set; }
    public ObjectId ShelfId { get; set; }

    ...etc...
}

【问题讨论】:

    标签: c# asp.net arrays mongodb ienumerable


    【解决方案1】:

    您收到此错误是因为 $ positional operator 只能使用一次,而在您的情况下,嵌套数组有多个级别。来自文档:

    位置 $ 运算符不能用于遍历多个数组的查询,例如遍历嵌套在其他数组中的数组的查询,因为 $ 占位符的替换是单个值

    要解决这个问题,您可以使用 MongoDB 3.6 中引入的 filtered positional 运算符。它允许您在更新路径中指定多个占位符,然后您可以使用arrayFilters 为这些占位符定义条件。

    var filter = Builders<StorageLocation>.Filter.And(
       Builders<StorageLocation>.Filter.Where(location => location.Id == id),
       Builders<StorageLocation>.Filter.Eq("StorageRooms._id", roomId));
    
    var arrayFilters = new List<ArrayFilterDefinition>();
    ArrayFilterDefinition<BsonDocument> sectionFilter = new BsonDocument("section._id", new BsonDocument("$eq", sectionId));
    ArrayFilterDefinition<BsonDocument> shelfFilter = new BsonDocument("shelf._id", new BsonDocument("$eq", shelfId));
    arrayFilters.Add(sectionFilter);
    arrayFilters.Add(shelfFilter);
    
    var updateOptions = new UpdateOptions { ArrayFilters = arrayFilters };
    
    
    var update =
        Builders<StorageLocation>.Update.Push("StorageRooms.$.StorageSections.$[section].StorageShelves.$[shelf].StorageSlotIds",
            storageSlotIds);
    
    await Col.UpdateOneAsync(filter, update, updateOptions);
    

    【讨论】:

    • 太棒了。太感谢了。现在我只需要更新 Azure Cosmos DB 版本以某种方式支持它......
    猜你喜欢
    • 2019-05-05
    • 2013-02-11
    • 2019-03-04
    • 1970-01-01
    • 1970-01-01
    • 2020-05-14
    • 1970-01-01
    • 1970-01-01
    • 2011-04-12
    相关资源
    最近更新 更多