c# - 如何使用 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...
}
解决方案
您收到此错误是因为$ 位置运算符只能使用一次,而在您的情况下,有多个级别的嵌套数组。从文档:
位置 $ 运算符不能用于遍历多个数组的查询,例如遍历嵌套在其他数组中的数组的查询,因为 $ 占位符的替换是单个值
要解决这个问题,您可以使用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);
推荐阅读
- go - 对多次组合的对象的类型断言
- excel - VBA将某些列复制到所有工作表
- prolog - Prolog 列表 - 重复的头部
- c - 如何将结构数组的属性作为参数发送给 C 中的函数?
- java - 当我准备好驱动程序类时,为“WAR”纸牌游戏创建一个类
- java - 例外:没有 Hibernate Session 绑定到线程,并且配置不允许在此处创建非事务性会话
- sql-server - 从 SQL Server 中的 XML 列中提取所有属性值
- javascript - javascript 中的 hasOwnProperty 和 Object.keys 无法按预期工作
- eclipse - Eclipse 中没有可用的源代码
- java - java - 如何在java中将两个二维数组相等?