问题
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. I tried couple of thing but nothing worked. I do not want to do 2 requests to avoid concurrency issue.
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
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.
If you have any idea please do not hesitate. Thanks Hak
回答1:
you could do it with a bulkwrite command such as this:
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. it uses MongoDB.Entities for brevity.
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();
}
}
}
回答2:
Hi Thank you for your answer, indeed we have 1 request now. 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:
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.
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:
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
来源:https://stackoverflow.com/questions/57657793/how-to-update-insert-if-not-exist-an-element-into-a-sub-array-of-a-document-in