How to Replace All in MongoDB Driver? w/ C# [duplicate] - c#

I was surprised to find that the following example code only updates a single document:
> db.test.save({"_id":1, "foo":"bar"});
> db.test.save({"_id":2, "foo":"bar"});
> db.test.update({"foo":"bar"}, {"$set":{"test":"success!"}});
> db.test.find({"test":"success!"}).count();
1
I know I can loop through and keep updating until they're all changed, but that seems terribly inefficient. Is there a better way?

Multi update was added recently, so is only available in the development releases (1.1.3). From the shell you do a multi update by passing true as the fourth argument to update(), where the the third argument is the upsert argument:
db.test.update({foo: "bar"}, {$set: {test: "success!"}}, false, true);
For versions of mongodb 2.2+ you need to set option multi true to update multiple documents at once.
db.test.update({foo: "bar"}, {$set: {test: "success!"}}, {multi: true})
For versions of mongodb 3.2+ you can also use new method updateMany() to update multiple documents at once, without the need of separate multi option.
db.test.updateMany({foo: "bar"}, {$set: {test: "success!"}})

Starting in v3.3 You can use updateMany
db.collection.updateMany(
<filter>,
<update>,
{
upsert: <boolean>,
writeConcern: <document>,
collation: <document>,
arrayFilters: [ <filterdocument1>, ... ]
}
)
In v2.2, the update function takes the following form:
db.collection.update(
<query>,
<update>,
{ upsert: <boolean>, multi: <boolean> }
)
https://docs.mongodb.com/manual/reference/method/db.collection.update/

For Mongo version > 2.2, add a field multi and set it to true
db.Collection.update({query},
{$set: {field1: "f1", field2: "f2"}},
{multi: true })

I've created a way to do this with a better interface.
db.collection.find({ ... }).update({ ... }) -- multi update
db.collection.find({ ... }).replace({ ... }) -- single replacement
db.collection.find({ ... }).upsert({ ... }) -- single upsert
db.collection.find({ ... }).remove() -- multi remove
You can also apply limit, skip, sort to the updates and removes by chaining them in beforehand.
If you are interested, check out Mongo-Hacker

To Update Entire Collection,
db.getCollection('collection_name').update({},
{$set: {"field1" : "value1", "field2" : "value2", "field3" : "value3"}},
{multi: true })

In the MongoDB Client, type:
db.Collection.updateMany({}, $set: {field1: 'field1', field2: 'field2'})
New in version 3.2
Params::
{}: select all records updated
Keyword argument multi not taken

MongoDB will find only one matching document which matches the query criteria when you are issuing an update command, whichever document matches first happens to be get updated, even if there are more documents which matches the criteria will get ignored.
so to overcome this we can specify "MULTI" option in your update statement, meaning update all those documnets which matches the query criteria. scan for all the documnets in collection finding those which matches the criteria and update :
db.test.update({"foo":"bar"},{"$set":{"test":"success!"}}, {multi:true} )

The following command can update multiple records of a collection
db.collection.update({},
{$set:{"field" : "value"}},
{ multi: true, upsert: false}
)

The updateMany() method has the following form:
db.collection.updateMany(
<filter>,
<update>,
{
upsert: <boolean>,
writeConcern: <document>,
collation: <document>,
arrayFilters: [ <filterdocument1>, ... ],
hint: <document|string> // Available starting in MongoDB 4.2.1
}
)
The restaurant collection contains the following documents:
{ "_id" : 1, "name" : "Central Perk Cafe", "violations" : 3 }
{ "_id" : 2, "name" : "Rock A Feller Bar and Grill", "violations" : 2 }
{ "_id" : 3, "name" : "Empire State Sub", "violations" : 5 }
{ "_id" : 4, "name" : "Pizza Rat's Pizzaria", "violations" : 8 }
The following operation updates all documents where violations are greater than 4 and $set a flag for review:
try {
db.restaurant.updateMany(
{ violations: { $gt: 4 } },
{ $set: { "Review" : true } }
);
} catch (e) {
print(e);
}

All latest versions of mongodb updateMany() is working fine
db.getCollection('workers').updateMany({},{$set: {"assignedVehicleId" : "45680"}});

I had the same problem , and i found the solution , and it works like a charm
just set the flag multi to true like this :
db.Collection.update(
{_id_receiver: id_receiver},
{$set: {is_showed: true}},
{multi: true} /* --> multiple update */
, function (err, updated) {...});
i hope that helps :)

You can use.`
Model.update({
'type': "newuser"
}, {
$set: {
email: "abc#gmail.com",
phoneNumber:"0123456789"
}
}, {
multi: true
},
function(err, result) {
console.log(result);
console.log(err);
}) `

Thanks for sharing this, I used with 2.6.7 and following query just worked,
for all docs:
db.screen.update({stat:"PRO"} , {$set : {stat:"pro"}}, {multi:true})
for single doc:
db.screen.update({stat:"PRO"} , {$set : {stat:"pro"}}, {multi:false})

Related

MongoDB C# 2.0 upserting sub item in collection [duplicate]

I have documents that looks something like that, with a unique index on bars.name:
{ name: 'foo', bars: [ { name: 'qux', somefield: 1 } ] }
. I want to either update the sub-document where { name: 'foo', 'bars.name': 'qux' } and $set: { 'bars.$.somefield': 2 }, or create a new sub-document with { name: 'qux', somefield: 2 } under { name: 'foo' }.
Is it possible to do this using a single query with upsert, or will I have to issue two separate ones?
Related: 'upsert' in an embedded document (suggests to change the schema to have the sub-document identifier as the key, but this is from two years ago and I'm wondering if there are better solutions now.)
No there isn't really a better solution to this, so perhaps with an explanation.
Suppose you have a document in place that has the structure as you show:
{
"name": "foo",
"bars": [{
"name": "qux",
"somefield": 1
}]
}
If you do an update like this
db.foo.update(
{ "name": "foo", "bars.name": "qux" },
{ "$set": { "bars.$.somefield": 2 } },
{ "upsert": true }
)
Then all is fine because matching document was found. But if you change the value of "bars.name":
db.foo.update(
{ "name": "foo", "bars.name": "xyz" },
{ "$set": { "bars.$.somefield": 2 } },
{ "upsert": true }
)
Then you will get a failure. The only thing that has really changed here is that in MongoDB 2.6 and above the error is a little more succinct:
WriteResult({
"nMatched" : 0,
"nUpserted" : 0,
"nModified" : 0,
"writeError" : {
"code" : 16836,
"errmsg" : "The positional operator did not find the match needed from the query. Unexpanded update: bars.$.somefield"
}
})
That is better in some ways, but you really do not want to "upsert" anyway. What you want to do is add the element to the array where the "name" does not currently exist.
So what you really want is the "result" from the update attempt without the "upsert" flag to see if any documents were affected:
db.foo.update(
{ "name": "foo", "bars.name": "xyz" },
{ "$set": { "bars.$.somefield": 2 } }
)
Yielding in response:
WriteResult({ "nMatched" : 0, "nUpserted" : 0, "nModified" : 0 })
So when the modified documents are 0 then you know you want to issue the following update:
db.foo.update(
{ "name": "foo" },
{ "$push": { "bars": {
"name": "xyz",
"somefield": 2
}}
)
There really is no other way to do exactly what you want. As the additions to the array are not strictly a "set" type of operation, you cannot use $addToSet combined with the "bulk update" functionality there, so that you can "cascade" your update requests.
In this case it seems like you need to check the result, or otherwise accept reading the whole document and checking whether to update or insert a new array element in code.
if you dont mind changing the schema a bit and having a structure like so:
{ "name": "foo", "bars": { "qux": { "somefield": 1 },
"xyz": { "somefield": 2 },
}
}
You can perform your operations in one go.
Reiterating 'upsert' in an embedded document for completeness
I was digging for the same feature, and found that in version 4.2 or above, MongoDB provides a new feature called Update with aggregation pipeline.
This feature, if used with some other techniques, makes possible to achieve an upsert subdocument operation with a single query.
It's a very verbose query, but I believe if you know that you won't have too many records on the subCollection, it's viable. Here's an example on how to achieve this:
const documentQuery = { _id: '123' }
const subDocumentToUpsert = { name: 'xyz', id: '1' }
collection.update(documentQuery, [
{
$set: {
sub_documents: {
$cond: {
if: { $not: ['$sub_documents'] },
then: [subDocumentToUpsert],
else: {
$cond: {
if: { $in: [subDocumentToUpsert.id, '$sub_documents.id'] },
then: {
$map: {
input: '$sub_documents',
as: 'sub_document',
in: {
$cond: {
if: { $eq: ['$$sub_document.id', subDocumentToUpsert.id] },
then: subDocumentToUpsert,
else: '$$sub_document',
},
},
},
},
else: { $concatArrays: ['$sub_documents', [subDocumentToUpsert]] },
},
},
},
},
},
},
])
There's a way to do it in two queries - but it will still work in a bulkWrite.
This is relevant because in my case not being able to batch it is the biggest hangup. With this solution, you don't need to collect the result of the first query, which allows you to do bulk operations if you need to.
Here are the two successive queries to run for your example:
// Update subdocument if existing
collection.updateMany({
name: 'foo', 'bars.name': 'qux'
}, {
$set: {
'bars.$.somefield': 2
}
})
// Insert subdocument otherwise
collection.updateMany({
name: 'foo', $not: {'bars.name': 'qux' }
}, {
$push: {
bars: {
somefield: 2, name: 'qux'
}
}
})
This also has the added benefit of not having corrupted data / race conditions if multiple applications are writing to the database concurrently. You won't risk ending up with two bars: {somefield: 2, name: 'qux'} subdocuments in your document if two applications run the same queries at the same time.

Find if an element in array has value equal to parent element value

Let's say we have a collection of documents like this one:
{
"_id" : ObjectId("591c54faf1c1f419a830b9cf"),
"fingerprint" : "3121733676",
"screewidth" : "1920",
"carts" : [
{
"cartid" : 391796,
"status" : "New",
"cart_created" : ISODate("2017-05-17T13:50:37.388Z"),
"closed" : false,
"items" : [
{
"brandid" : "PIR",
"cai" : "2259700"
}
],
"updatedon" : ISODate("2017-05-17T13:51:24.252Z")
},
{
"cartid" : 422907,
"status" : "New",
"cart_created" : ISODate("2017-10-23T08:57:06.846Z"),
"closed" : false,
"items" : [
{
"brandid" : "PIR",
"cai" : "IrHlNdGtLfBoTlKsJaRySnM195U"
}
],
"updatedon" : ISODate("2017-10-23T09:46:08.579Z")
}
],
"createdon" : ISODate("2016-11-08T10:29:55.120Z"),
"updatedon" : ISODate("2017-10-23T09:46:29.486Z")
}
How do you extract only the documents where no item in the array $.carts have $.carts.closed set to true and $.carts.updatedon greater than $.updatedon minus 3 days ?
I know how to do find all the documents where no item in the array satisfy the condition $and: [closed: {$eq: true}, {updatedon: {$gt : new ISODate("2017-10-20T20:15:31Z")}}]
But how can you reference the parent element $.updatedon for the comparison?
In plain mongodb shell query language it would aleady be of help.
But I am actually accessing it using c# driver, so my query filter is like this:
FilterDefinition<_visitorData> filter;
filter = Builders<_visitorData>.Filter
.Gte(f => f.updatedon, DateTime.Now.AddDays(-15));
filter = filter & (
Builders<_visitorData>.Filter
.Exists(f => f.carts, false)
| !Builders<_visitorData>.Filter.ElemMatch(f =>
f.carts, c => c.closed && c.updatedon > DateTime.Now.AddDays(-15)
)
);
How can I replace DateTime.Now.AddDays(-15) with a reference to the document root element updatedon?
You can project the difference of carts.updatedon and updatedon and then filter out the results from this aggregation pipeline.
coll.aggregate([{'$unwind':'$carts'},
{'$match':{'closed':{'$ne':true}}},
{'$project':{'carts.cartid':1,'carts.status':1,'carts.cart_created':1,'carts.closed':1,'carts.items':1,'carts.updatedon':1,'updatedon':1,'diff':{'$subtract':['$carts.updatedon','$createdon']}}},
{'$match': {'diff': {'$gte': 1000 * 60 * 60 * 24 * days}}}])
days = 3 will filter out results more than 3 days difference documents.
I have just given the example of how you can use $subtract to find date difference and filter documents based on that.
well I was in a similar situation few days back.
I tackled it by using Jobject of Newtonsoft.Json.
Create a function to return bool which actually process each document take it as input.
Jobject jOb=Jobject.parse(<Your document string>);
JArray jAr=JArray.Parse(jOb["carts"]);
If(jOb["updateon"]=<Your Business Validation>)
{
foreach(var item in jAr)
if(item["closed"]==<Your validation>){ return true}
}
return false;
I hope this helps :)
If you handling with any null values in those properties then please use Tryparse and out variable.

Multiple Filters and Updates in One Mongo Query [duplicate]

I have a collection which elements can be simplified to this:
{tags : [1, 5, 8]}
where there would be at least one element in array and all of them should be different. I want to substitute one tag for another and I thought that there would not be a problem. So I came up with the following query:
db.colll.update({
tags : 1
},{
$pull: { tags: 1 },
$addToSet: { tags: 2 }
}, {
multi: true
})
Cool, so it will find all elements which has a tag that I do not need (1), remove it and add another (2) if it is not there already. The problem is that I get an error:
"Cannot update 'tags' and 'tags' at the same time"
Which basically means that I can not do pull and addtoset at the same time. Is there any other way I can do this?
Of course I can memorize all the IDs of the elements and then remove tag and add in separate queries, but this does not sound nice.
The error is pretty much what it means as you cannot act on two things of the same "path" in the same update operation. The two operators you are using do not process sequentially as you might think they do.
You can do this with as "sequential" as you can possibly get with the "bulk" operations API or other form of "bulk" update though. Within reason of course, and also in reverse:
var bulk = db.coll.initializeOrderedBulkOp();
bulk.find({ "tags": 1 }).updateOne({ "$addToSet": { "tags": 2 } });
bulk.find({ "tags": 1 }).updateOne({ "$pull": { "tags": 1 } });
bulk.execute();
Not a guarantee that nothing else will try to modify,but it is as close as you will currently get.
Also see the raw "update" command with multiple documents.
If you're removing and adding at the same time, you may be modeling a 'map', instead of a 'set'. If so, an object may be less work than an array.
Instead of data as an array:
{ _id: 'myobjectwithdata',
data: [{ id: 'data1', important: 'stuff'},
{ id: 'data2', important: 'more'}]
}
Use data as an object:
{ _id: 'myobjectwithdata',
data: { data1: { important: 'stuff'},
data2: { important: 'more'} }
}
The one-command update is then:
db.coll.update(
'myobjectwithdata',
{ $set: { 'data.data1': { important: 'treasure' } }
);
Hard brain working for this answer done here and here.
Starting in Mongo 4.4, the $function aggregation operator allows applying a custom javascript function to implement behaviour not supported by the MongoDB Query Language.
And coupled with improvements made to db.collection.update() in Mongo 4.2 that can accept an aggregation pipeline, allowing the update of a field based on its own value,
We can manipulate and update an array in ways the language doesn't easily permit:
// { "tags" : [ 1, 5, 8 ] }
db.collection.updateMany(
{ tags: 1 },
[{ $set:
{ "tags":
{ $function: {
body: function(tags) { tags.push(2); return tags.filter(x => x != 1); },
args: ["$tags"],
lang: "js"
}}
}
}]
)
// { "tags" : [ 5, 8, 2 ] }
$function takes 3 parameters:
body, which is the function to apply, whose parameter is the array to modify. The function here simply consists in pushing 2 to the array and filtering out 1.
args, which contains the fields from the record that the body function takes as parameter. In our case, "$tag".
lang, which is the language in which the body function is written. Only js is currently available.
In case you need replace one value in an array to another check this answer:
Replace array value using arrayFilters

ElasticSearch.net NEST use a Wildcard index in query

Hi I'm new to Elastic Search and I'm making some experiments to understand the basics of query through ElasticSearch.net and NEST.
I'm trying to translate a query with this syntax:
curl -XGET 'http://myserver.com:9200/myindexes-*/XY/_search/?size=1000&pretty=1' -d '
{
"query": {
"bool": {
"filter": [
{ "match": { "LOGTYPE" : "XY" }},
{ "match": { "USER" : "mrossi" }},
{ "wildcard": { "DATA": "m.rossi*" }},
{ "match": { "CODE" : "WZ" }},
{ "range": { "timestamp": { "gte": "2015-05-02" }}}
]
}
}
}
'
I've found how to build the multiple field query, (thanks to those who posted it) but I still need to understand how to create the Wildcard index in the query.
The team feeding the Elastic Search engine has created an index in the format MyIndexes-YYYYMMDD with an index per each date because each date has millions of rows of data (they are log files). I haven't yet found a way to make a query on more than one index, if it is so simple as writing the wildcard in the index name or if I need to do something different.
If you have any clue, Thank you in advance.
Edited after some tests on the answer received: I've tried some tests and even if the call to elastic search seems to be successful reading the results I'm not able to understand if I have a problem in my query or if there are no data.
Valid NEST response built from a successful low level call on POST: /myindex-%2A/ml/_search?pretty=true
# Audit trail of this API call:
- [1] HealthyResponse: Node: http://username:pwd#mydomain.com:9200/ Took: 00:00:00.0580006
# Request:
{"size":1000,"query":{"bool":{"filter":[{"match":{"LOGTYPE":{"query":"XY"}}}]}}}
# Response:
{
"took" : 31,
"timed_out" : false,
"_shards" : {
"total" : 270,
"successful" : 270,
"failed" : 0
},
"hits" : {
"total" : 0,
"max_score" : null,
"hits" : [ ]
}
}
The above data is what I can see from the DebugInformation of the response. My uncertainty is connected with how the POST command is built, in fact even if my entity is named XY uppercase in the post appears in lowercase, and also the pretty variable has a value of true instead of 1. the size parameter is set inside the request and not as a variable of the POST as in the sample I posted in the first message.
I've simplified the filters to set just one filter and also in the request data I see that in my sample the "XY" filter has a query: instead of nothing as it is in the sample.
so at the moment the query seems to be sintactically correct but I don't know if it is correct on my index data.
can you suggest how to try and use something like select top 100 * from my table just to see if I can find the data, and then try and implement filters???
thank you again
You can use a wildcard in the Index name portion of your query.
var client = new ElasticClient();
client.Search<MyObject>(s=>s
.Index("myindexes-*")
...
)
You should be able to use the wildcard in the index... is your query not working?

How to implement MongoDB nested $elemMatch Query in C#

I have a MongoDB collection in the following format.
{
"_id" : ObjectId("56c6f03ffd07dc1de805e84f"),
"Details" : {
"a" : [
[ {
"DeviceID" : "log0",
"DeviceName" : "Dev0"
},
{
"DeviceID" : "log1",
"DeviceName" : "Dev1"
}
],
[ {
"DeviceID" : "Model0",
"DeviceName" : "ModelName0"
},
{
"DeviceID" : "Model1",
"DeviceName" : "ModelName1"
}
]
]
}
}
And I am trying to fetch all the documents where the DeviceName in array "a" contains a particular value, say "Name0". However I could get the desired result while using below Mongo query:
db.test_collection.find({"Details.a":{$elemMatch:{$elemMatch:{DeviceName : /.*Name0.*/}}}});
Now I am struggling to implement the above query in C#. Can anyone guide me with that?
so far I have tried the below code and it was not working as expected
query = Query.And(Query.ElemMatch("Details.a", Query.And(Query.ElemMatch("DeviceName", Query.Matches("DeviceName", new BsonRegularExpression("Name0"))))));
Thanks in advance
Well, honestly writing queries in C# are bit tricky but you can always play a trick.
var bsonQuery = "{'Details.a':{$elemMatch:{$elemMatch:{DeviceName : /.*Name0.*/}}}}";
var filter = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<BsonDocument>(bsonQuery);
var result = col.FindSync (filter).ToList();
I'm deserializing a plain MongoDB queries into a BsonDocument which in return I'm passing to FindAsync as filter.
In the end, you'll have desired outcome in variable result.
Note: I'm assuming MongoDB connection has been established and variable col holds reference to MongoDB collection.
EDIT: Please see following link https://groups.google.com/forum/#!topic/mongodb-csharp/0dcoVlbFR2A. Now it's confirmed that C# driver doesn't support nameless filters so writing above query using Buidlers<BsonDocument>.Filter at moment is not supported.
Long story short, you are left with only one choice and that is to query as I mentioned above in my solution.

Categories