Как получить доступ к глубоко вложенному массиву с MongoDB (ASP.NET Core 2.2) - PullRequest
0 голосов
/ 03 февраля 2019

Я разрабатываю систему управления запасами с 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 post)

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

1 Ответ

0 голосов
/ 04 февраля 2019

Вы получаете эту ошибку, потому что $ позиционный оператор может использоваться только один раз, в то время как в вашем случае есть несколько уровней вложенных массивов.Из документов:

Позиционный оператор $ нельзя использовать для запросов, которые пересекают более одного массива, таких как запросы, пересекающие массивы, вложенные в другие массивы, поскольку замена для $ placeholder является одиночнойзначение

Чтобы исправить это, вы можете использовать отфильтрованный позиционный оператор , который был представлен в 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);
...