如何使用 MongoDB 访问深层嵌套数组(ASP.NET Core 2.2)
How to access deeply nested array with MongoDB (ASP.NET Core 2.2)
我正在使用 MongoDB 设计一个库存管理系统。我有以下数据库结构:
inventory
└─storage_slots
└─storage_locations
...etc...
每添加一个新的Slot
,就会在storage_locations
集合中添加一棵代表slot在层次结构中的位置的树来表示它的位置(根据location, room, section, shelf ).到目前为止,我已经成功地添加了一个新项目,其中 none 个位置字段已被使用:(插槽也被添加到 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
只是插槽的集合。
但是,如果字段已经存在于层次结构中,则以下代码是运行:(我从 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...
}
您收到此错误是因为 $ positional operator 只能使用一次,而在您的情况下有多层嵌套数组。来自文档:
The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value
要解决此问题,您可以使用 filtered positional 运算符,该运算符在 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);
我正在使用 MongoDB 设计一个库存管理系统。我有以下数据库结构:
inventory
└─storage_slots
└─storage_locations
...etc...
每添加一个新的Slot
,就会在storage_locations
集合中添加一棵代表slot在层次结构中的位置的树来表示它的位置(根据location, room, section, shelf ).到目前为止,我已经成功地添加了一个新项目,其中 none 个位置字段已被使用:(插槽也被添加到 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...
}
您收到此错误是因为 $ positional operator 只能使用一次,而在您的情况下有多层嵌套数组。来自文档:
The positional $ operator cannot be used for queries which traverse more than one array, such as queries that traverse arrays nested within other arrays, because the replacement for the $ placeholder is a single value
要解决此问题,您可以使用 filtered positional 运算符,该运算符在 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);