首页 > 解决方案 > 如何使用 MongoDB (ASP.NET Core 2.2) 访问深度嵌套的数组

问题描述

我正在用 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只是插槽的集合。

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

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.netarraysmongodbienumerable

解决方案


您收到此错误是因为$ 位置运算符只能使用一次,而在您的情况下,有多个级别的嵌套数组。从文档:

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

要解决这个问题,您可以使用MongoDB 3.6 中引入的过滤位置运算符。它允许您在更新路径中指定多个占位符,然后您可以使用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);

推荐阅读