[英]How to update/ insert if not exist an element into a sub array of a document in MongoDB C# driver
I have a document which contains an array of document (more or less complex), I would like to create 1 query which will update the element of the array based on a filter, and if no element match insert the element into the array. 我有一个包含一个文档数组(或多或少复杂)的文档,我想创建1个查询,该查询将基于过滤器更新该数组的元素,如果没有匹配的元素,则将该元素插入到数组中。 I tried couple of thing but nothing worked. 我尝试了几件事,但没有任何效果。 I do not want to do 2 requests to avoid concurrency issue. 我不想做2个请求以避免并发问题。
Below is my document model, which model a driver with the car he owns 以下是我的文档模型,该模型使用自己拥有的汽车为驾驶员建模
public string Driver{ get; set; }
public Cars[] OwnedCars{ get; set; }
Let us suppose that I have a model like this. 让我们假设我有一个这样的模型。
The car can be defined as below: 汽车可以定义如下:
Car {
color: string;
plateNumber: string
insuranceNumber: string,
options: object
.
.
.
}
The thing is that I can change the color of the car, a buy a new car. 问题是我可以改变汽车的颜色,买一辆新车。
I would like one request which enables to add or update a document based on the plate number for the user. 我想要一个能够根据用户的车牌号添加或更新文档的请求。
I tried several thing: 我尝试了几件事:
I created a filter which looks like this: driverId, car and plateNumber are input 我创建了一个过滤器,如下所示:输入了driverId,car和plateNumber
var filter = Builders < CarModel > .Filter.And(
Builders < ViewConfigCollStorageModel > .Filter.Eq(x = > x.Driver, driverId),
Builders < ViewConfigCollStorageModel > .Filter.ElemMatch(x = > x.ownedCars, x = > x.insuranceNumber == plateNumber));
the update could look like this: 更新可能如下所示:
var update = Builders<CarModel>.Update.Set(x => x.ownedCars[-1], car);
var res = await ViewConfigCollection.RawCollection.UpdateOneAsync(filter, update, option);
in the option we can put 在选项中我们可以放
var option = new UpdateOptions() { IsUpsert = true};
The set works well if the car is found, but return an error due to the positional operator if the car does not exist. 如果找到汽车,该集合将很好地工作,但是如果汽车不存在,则由于位置运算符而返回错误。
I tried AddToSet operator but if a property does not match like the color it insert a new object whereas I would like to update the existing one. 我尝试了AddToSet运算符,但如果某个属性与颜色不匹配,它将插入一个新对象,而我想更新现有对象。
If you have any idea please do not hesitate. 如果您有任何想法,请不要犹豫。 Thanks Hak 谢谢哈克
you could do it with a bulkwrite command such as this: 您可以使用如下的bulkwrite命令来做到这一点:
db.StorageModel.bulkWrite([
{
updateOne: {
filter: {
"DriverID": "321",
"OwnedCars": {
"$not": {
"$elemMatch": {
"PlateNumber": "ABC123"
}
}
}
},
update: {
"$push": {
"OwnedCars": {
"PlateNumber": "ABC123",
"Color": "White"
}
}
}
}
},
{
updateOne: {
filter: {
"DriverID": "321",
"OwnedCars": {
"$elemMatch": {
"PlateNumber": "ABC123"
}
}
},
update: {
"$set": {
"OwnedCars.$": {
"PlateNumber": "ABC123",
"Color": "White"
}
}
}
}
}
])
here's the c# code that generated the above mongodb command. 这是生成上面的mongodb命令的C#代码。 it uses MongoDB.Entities for brevity. 为了简洁起见,它使用MongoDB.Entities。
using MongoDB.Entities; using System.Linq; namespace StackOverflow { public class Program { public class StorageModel : Entity { public string DriverID { get; set; } public Car[] OwnedCars { get; set; } } public class Car { public string PlateNumber { get; set; } public string Color { get; set; } } private static void Main(string[] args) { new DB("test"); (new StorageModel { DriverID = "321", OwnedCars = new[] { new Car { PlateNumber = "ABC123", Color = "Red"} } }).Save(); var updatedCar = new Car { PlateNumber = "ABC123", Color = "White" }; var bulk = DB.Update<StorageModel>(); bulk.Match(s => s.DriverID == "321" && !s.OwnedCars.Any(c => c.PlateNumber == updatedCar.PlateNumber)) .Modify(b => b.Push(s => s.OwnedCars, updatedCar)) .AddToQueue(); bulk.Match(s => s.DriverID == "321" && s.OwnedCars.Any(c => c.PlateNumber == updatedCar.PlateNumber)) .Modify(b => b.Set(s => s.OwnedCars[-1], updatedCar)) .AddToQueue(); bulk.Execute(); } } }
Hi Thank you for your answer, indeed we have 1 request now. 您好,谢谢您的回答,确实我们现在有1个请求。 However both the requests are executed. 但是,两个请求都被执行。 The update is always call even if the the insert occurs before. 即使插入发生在之前,也总是调用更新。
The solution with 2 requests looks like this: 具有2个请求的解决方案如下所示:
var res = await CarCollection.UpdateOneAsync(filterTryInsert, updateTryInsert);
if(res.UpsertedId == null && res.MatchedCount == 0 && res.ModifiedCount ==0)
{
var resUp = await
CarCollection.UpdateOneAsync(filterUpdate,updateUp);
return resUp.ModifiedCount > 0;
}
Here if the car does not exist for the user it is directly inserted, upsertedId won't be null so the if block won't be reached. 在这里,如果用户不存在汽车,则无法直接插入汽车,upsertedId不会为null,因此不会到达if块。
The issue with this is a concurrency issue, if the same user tries to call an update in one browser and a delete in another one we could end up doing this: 与此相关的问题是并发问题,如果同一用户尝试在一个浏览器中调用一个更新,而在另一个浏览器中调用一个删除,我们最终可能会这样做:
Push => Delete => Update the update in this case will lead to an error because the element does not exist. 推送=>删除=>更新这种情况下的更新将导致错误,因为该元素不存在。
The one request solution is safer because it let the DB managing the concurrency avoiding those kind of issues. 单请求解决方案比较安全,因为它可以让数据库管理并发避免此类问题。
The C# code of the Ryan solution looks like this: Ryan解决方案的C#代码如下所示:
var bulkOps = new List<WriteModel<CarModel>>() { modelPush, modelUpdate };
var res = await CarCollection.BulkWriteAsync(bulkOps);
return res.ModifiedCount > 0;
Now the question is could we with the bulk write avoid executing both requests all the time? 现在的问题是,批量写入是否可以始终避免执行两个请求?
Thanks in Advance: 提前致谢:
Hak 陈克勤
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.