Update embedded document details in MongoDB - c#

In MongoDB, if I have a document structure as follows:
{ "_id" : { "$binary" : "jchPoPd7PUS1w+sR7is23w==", "$type" : "03" },
"companies" :
[
{ "_id" : { "$binary" : "jchPoPd7PUS1w+sR7is23w==", "$type" : "03" },
"name" : "Google" },
{ "_id" : { "$binary" : "jchPoPd7PUS1w+sR7is23w==", "$type" : "03" },
"name" : "Greenfin" },
{ "_id" : { "$binary" : "jchPoPd7PUS1w+sR7is23w==", "$type" : "03" },
"name" : "Zynet" }
],
"firstname" : "Peter",
"surname" : "Smith" }
(i.e. a Person document with a Companies array embedded within the person document), then how do I update ALL occurrences of a specific Company (targetting via the company _id) with a single query+update?
I have tried the following:
MongoCollection personCollection = mdb.GetCollection("person");
BsonBinaryData bin = new BsonBinaryData(new Guid("0AE91D6B-A8FA-4D0D-A94A-91D6AC9EE343"));
QueryComplete query = Query.EQ("companies._id", bin);
var update = Update.Set("companies.name", "GreenfinNewName");
SafeModeResult result = personCollection.Update(query, update, UpdateFlags.Multi);
but it doesn't work. I guess my question boils down to two questions: how do I target the embedded companies in the initial query, and then how do I set the new company name in the Set statement. Note: In this example I have chosen to "denormalise" the company data, and embed it in each person document, so there may be several person documents with the same company id and name. Thanks very much.
UPDATE:
Using Bugai13's technique I've got closer. This works:
MongoCollection personCollection = mdb.GetCollection("person");
QueryComplete query = Query.EQ("companies.name", "Bluefin");
var update = Update.Set("companies.$.companynotes", "companynotes update via name worked");
SafeModeResult result = personCollection.Update(query, update, UpdateFlags.Multi, SafeMode.True);
But this doesn't work:
MongoCollection personCollection = mdb.GetCollection("person");
BsonBinaryData bin = new BsonBinaryData(new Guid("0AE91D6B-A8FA-4D0D-A94A-91D6AC9EE343"));
Builders.QueryComplete query = Query.EQ("companies._id", bin);
var update = Update.Set("companies.$.companynotes", "companynotes update via id worked");
SafeModeResult result = personCollection.Update(query, update, UpdateFlags.Multi, SafeMode.True);
So, I can't yet update using the primary key, which is what I need to do...

I suppose use you should take a look into positional operator in mongodb:
var update = MongoDB.Driver.Builders.Update
.Set("companies.$.name", "GreenfinNewName");
^^
all magic here
Note: above code will update only first matched item in array. So if you have two company in nested array with name = GreenfinNewName above code will update only first matched.
Note: UpdateFlags.Multi means multiple documents, but not multiple items in nested array.
Update:
QueryComplete query = Query.EQ("companies._id",
BsonValue.Create(new Guid("0AE91D6B-A8FA-4D0D-A94A-91D6AC9EE343")));
var update = Update.Set("companies.$.companynotes", "companynotes");
personCollection.Update(query, update, UpdateFlags.Multi, SafeMode.True);
Hope this help!

Just wanted to say thanks to you both. Am learning Mongo and this thread helped.
I'm using the 10gen C# driver, and for reference this is my code:
MongoServer mongo = MongoServer.Create();
MongoDatabase db = mongo.GetDatabase("test");
MongoCollection<BsonDocument> coll = db["contacts"];
BsonDocument doc = new BsonDocument();
doc["FirstName"] = "Daniel";
doc["LastName"] = "Smith";
doc["Address"] = "999 Letsby Avenue";
doc["City"] = "London";
doc["County"] = "Greater London";
doc["Postcode"] = "N13";
coll.Insert<BsonDocument>(doc);
QueryComplete qSel = Query.EQ("Postcode", "N13");
MongoCursor<BsonDocument> cur = coll.Find(qSel);
foreach (BsonDocument bdoc in cur)
{
Console.WriteLine(bdoc["FirstName"] + ":" + bdoc["Address"]);
}
UpdateBuilder docTwo = Update.Set("Postcode", "MK10");
coll.Update(qSel, docTwo, UpdateFlags.Multi);
QueryDocument qSel2 = new QueryDocument("FirstName", "Daniel");
MongoCursor<BsonDocument> cur2 = coll.Find(qSel2);
foreach (BsonDocument bsdoc in cur2)
{
Console.WriteLine(bsdoc["FirstName"] + " : " + bsdoc["Postcode"]);
}

Related

UpdateOneModel, with Upsert - error _id Mongodb C# MongoDB.Driver

I am trying to perform a bulkwrite with c# https://mongodb.github.io/mongo-csharp-driver/2.7/reference/driver/crud/writing/#bulk-writes - I could have a list of documents from 70-80K.
var correlationIdFilter = Builders<BsonDocument>.Filter.AnyIn("CorrelationId", ninKeysRecon);
var missingData = collection.Find(correlationIdFilter).ToList();
Missing Data Data Sample
{
"_id" : ObjectId("61dd323bfe35f25bb2dcde8e"),
"CorrelationId" : "17bd621d-e47f-4ab1-9004-9543294a4549",
"Key" : "123",
"Date" : "2016-06-28T00:00:00",
"CurrentDate" : ISODate("2022-01-11T07:31:07.011+0000"),
"SourceSystem" : "abc",
"SourceEntity" : "source"
},
{
"_id" : ObjectId("61dd323bfe35f25bb2dcd123"),
"CorrelationId" : "18bd621d-e47f-4ab1-9004-9543294a4549",
"Key" : "123232324",
"Date" : "2016-06-28T00:00:00",
"CurrentDate" : ISODate("2022-01-11T07:31:07.011+0000"),
"SourceSystem" : "abc",
"SourceEntity" : "source"
},
.
.
.
{100K Documents}
Then I createoptions and bulkOps
var options = new UpdateOptions { IsUpsert = true };
var bulkOps = new List<WriteModel<BsonDocument>>();
I dont know if I need to loop through missingData to create a new UpdateOneModel() but I am struggling to see where or how to create a filter because when I set _id as a filter, I get the error _id field is immutable. I do not want to overwrite the _id but I simply want to achieve something like this
collection.UpdateMany(correlationIdFilter, missingBson, options);
Or if I have to create a for loop, I have tried:
foreach(var data in missingBson)
{
var upsert = new UpdateOneModel<BsonDocument>(
new BsonDocument("_id", 1),
new BsonDocument("$set", data)) { IsUpsert = true };
bulkOps.Add(upsert);
}
collection.BulkWrite(bulkOps);
I get the error:
WriteErrors: [ { Category : "Uncategorized", Code : 66, Message : "Performing an update on the path '_id' would modify the immutable field '_id'" } ].'
removing - { IsUpsert = true } runs fine but doesn't do any upserting which I need.
Thank you
The first argument passed to UpdateOneModel is the filter. The filter tells MongoDB which document you want to update. You pass new BsonDocument("_id", 1), which tells MongoDB you want to update the document with an _id of 1. _id is a special field in MongoDB documents:
The field name _id is reserved for use as a primary key; its value must be unique in the collection, is immutable, and may be of any type other than an array. If the _id contains subfields, the subfield names cannot begin with a ($) symbol
_id is immutable, meaning it cannot be changed. This is an issue because the data you are passing to $set contains an _id field that is not 1.
Instead, in the filter document you should be passing the _id of the document you are trying to insert.
foreach(var data in missingBson)
{
var upsert = new UpdateOneModel<BsonDocument>(
new BsonDocument("_id", data._id),
new BsonDocument("$set", data)) { IsUpsert = true };
bulkOps.Add(upsert);
}
This way the _id fields match, and you are not attempting to update a document with an _id of 1 for each write operation.
try this, with bulkWriteAsycn func. You can use this func. with a timer
var requests = new List<WriteModel<T>>();
foreach(var data in dataList)
{
requests.Add(
new UpdateOneModel<T>
(
filter,
Builders<T>.Update(x=>x.key, data.key) // custom updates
)
{IsUpsert=true};
)
}
await collection.BulkWriteAsync(requests);
this filter is your custom filter for updated fields.
Needs attention for new record (all fields are possibly)
Filter Ex:
Builders.Filter.Where(item=> item.id == data.id)
see UpdateOneModel.IsUpsert Property

translating mongo query to C# by using Filter

Is there any way to use Filters in C# and translate this mongo query?
{'EmailData.Attachments.Files': {$all: [{Name: 'a.txt'},{Name: 'b.txt'},{Name:'c.txt'}], $size: 3}}
my data model is like:
{
"_id": ObjectId("5f0a9c07b001406068c073c1"),
"EmailData" : [
{
"Attachments" : {
"Files" : [
{
"Name" : "a.txt"
},
{
"Name" : "b.txt"
},
{
"Name" : "c.txt"
}
]
}
}
]
}
I have something like this in my mind:
var Filter =
Builders<EmailEntity>.Filter.All(s => s.EmailData????);
or something like:
var Filter =
Builders<EmailEntity>.Filter.ElemMatch(s => s.EmailData???)
I was wondering is there any way in the above filter to use All inside ElemMatch?
The difficulty here is that EmailData.Attachments.Files is an array within another array so C# compiler will get lost when you try to use Expression Trees.
Thankfully there's another approach when you need to define a field using MongoDB .NET driver. You can take advantage of StringFieldDefinition<T> class.
Try:
var files = new[] { new FileData(){ Name = "a.txt"}, new FileData() { Name = "b.txt" }, new FileData() { Name = "c.txt" } };
FieldDefinition<EmailEntity> fieldDef = new StringFieldDefinition<EmailEntity>("EmailData.Attachments.Files");
var filter = Builders<EmailEntity>.Filter.And(
Builders<EmailEntity>.Filter.All(fieldDef, files),
Builders<EmailEntity>.Filter.Size(fieldDef, 3));
var result= collection.Find(filter).ToList();

Building Mongo aggregation pipeline query with $regex search using C# Mongo driver

I'm trying to build aggregate pipeline with value search according to the value with the C# mongo driver.
The first stage would to filter where KeyA is "A" and then the second stage would be to do the search where I have a certain list of keys that I need to go over and compare their value with a regex, according to documentation the regex only checks strings and I need to check any type that the value can hold.
I'm adding a model and code examples for what I have done so far but couldn't get to the expected end result, any help would be appriciated.
Model for example:
{
"_id" : ObjectId(),
"KeyA" : "A",
"KeyB" : NumberInt(5),
"KeyNestedObj" : [
{
"KeyC" : "C"
},
{
"KeyD" : "D"
},
{
"KeyE" : NumberInt(5)
}
]
}
Mongo shell query:
db.MongoTests.aggregate([{ "$match": { "KeyA": "A" }},
{ "$match": { "$or": [{ "KeyNestedObj": { "$elemMatch": { "KeyC": /^.*?Regex.*?$/ } } }, { "KeyNestedObj": { "$elemMatch": { "KeyE": /^.*?Regex.*?$/ }}}]}}])
C# query build:
var searchableFields = new List<SearchableField>
{
new SearchableField{ Field = "KeyC" },
new SearchableField{ Field = "KeyE" }
};
var searchableFieldsFilter = new List<FilterDefinition<Model>>();
var commonSearchBuilder = Builders<Model>.Filter;
var nestedObjSearchBuilder = Builders<KeyNestedObj>.Filter;
var searchValueRegex = new BsonRegularExpression($"^.*?{searchValue}.*?$");
foreach (var searchableField in searchableFields)
{
var searchNestedField =
commonSearchBuilder.ElemMatch(x => x.KeyNestedObj,
nestedObjSearchBuilder.Regex(searchableField.Field, searchValueRegex));
searchableFieldsFilter.Add(searchNestedField);
}
return Builders<ActivityDataModel>.Filter.And(searchableFieldsFilter);
Code aggregation query:
var renderedSearchQuery = searchStage.Render(mongoContext.Data.DocumentSerializer,
mongoContext.Data.Settings.SerializerRegistry);
var results= mongoContext.Data.Aggregate().Match(filterQuery).AppendStage<Model>(renderedSearchQuery).ToListAsync();

How to find documents by ObjectId?

I have the following document in a mongodb collection
{
"_id" : "52bbd9bef2ba1f37f4f010c1",
"Name" : "Some Name",
"TypeId" : 1
}
I now want to edit this document using the c# driver
BsonDocument doc = BsonDocument.Parse(json);
IMongoQuery query = Query.And(Query.EQ("_id", doc["_id"]));
UpdateBuilder ub = MongoDB.Driver.Builders.Update.Set('Name', 'New Name');
WriteConcernResult updatedBook = _collection.Update(query, ub);
if (!updatedBook.UpdatedExisting)
{
// I always end here
}
It is also weird that the docs tell that _collection.Update(query, ub) should return a BsonDocument while they return a WriteConcernResult. Anyway if I use another property then _id I find the record.
It seems MongoDB is expecting a ObjectId() function wrapper around a ObjectId, isn't it? At least I need this in my tests while using MongoVUE.
Edit
If I try to find a record with this query
{
"_id" : "52bbd9bef2ba1f37f4f010c1"
}
I get nothing. If I use this
{
"_id" : ObjectId("52bbd9bef2ba1f37f4f010c1"),
}
it worked. So much for the Javascript way. AFAIK I have two ways to update a record with the native .net driver:
FindAndModifyResult
WriteConcernResult
But both ways expecting a BsonValue as value where ObjectId seems not to be a valid one...
So I tried using
IMongoQuery query = Query.And(Query.EQ("_id", doc["_id"]));
as described above but that is the same like
{
"_id" : "52bbd9bef2ba1f37f4f010c1"
}
So I wonder how to update a document by it's ObjectId?
When using the currently available Nuget published MongoDB driver for .NET, I can confirm that this code as an example works properly:
MongoClient client = new MongoClient(); // connect to localhost
MongoServer server = client.GetServer();
MongoDatabase test = server.GetDatabase("test");
MongoCollection examples = test.GetCollection("examples");
string id = "52bc958ca45026c2ff24f90b";
IMongoQuery query = Query.EQ("_id", ObjectId.Parse(id));
UpdateBuilder ub = Update.Set("Name", "George");
WriteConcernResult updatedBook = examples.Update(query, ub);
Using this collection data:
> db.examples.remove()
> db.examples.insert({Name:"Larry"})
> db.examples.find()
{ "_id" : ObjectId("52bc958ca45026c2ff24f90b"), "Name" : "Larry" }
Then, run the C# code above and check the collection again:
> db.examples.find()
{ "Name" : "George", "_id" : ObjectId("52bc958ca45026c2ff24f90b") }
While the WriteConcernResult class itself isn't a BsonDocument, the Response property contains the response from the command.
I also removed the unnecessary Query.And, as it's doesn't do anything when there's only one condition:
query = { "_id" : ObjectId("52bc958ca45026c2ff24f90b") }
ub = { "$set" : { "Name" : "George" } }

c# mongodb BsonDocument array

the below code seems to work except that the values are not actually saved to the existing document.
MongoServer mongo = MongoServer.Create();
mongo.Connect();
var db = mongo.GetDatabase("forms");
mongo.RequestStart(db);
var collection = db.GetCollection("forms");
var query = new QueryDocument("_id",ObjectId.Parse(Id));
var resultsCursor = collection.Find(query);
foreach (BsonDocument item in resultsCursor)
{
var formFields = new BsonArray();
formFields.Add(new BsonDocument
{
{"ID", ObjectId.GenerateNewId()},
{"NAME",name},
{"TYPE",type}
}
);
collection.Save(item.Add("fields",formFields));
I say it works because this the result of getlasterror run immediately after the save:
db.GetLastError()
{MongoDB.Driver.GetLastErrorResult}
base {MongoDB.Driver.CommandResult}: {MongoDB.Driver.GetLastErrorResult}
DocumentsAffected: 1
HasLastErrorMessage: false
LastErrorMessage: null
UpdatedExisting: true
I'm missing something (probably something simple...).
Thanks for any assistance.
The code works fine (well, with a few tweaks to make it compile standalone and to fit my test environment):
MongoServer mongo = MongoServer.Create();
mongo.Connect();
var db = mongo.GetDatabase("test");
// mongo.RequestStart(db); // removed as it's not correct
var collection = db.GetCollection("so");
var query = new QueryDocument("_id", "12345"); // hard-coded an ID for test
var resultsCursor = collection.Find(query);
foreach (BsonDocument item in resultsCursor)
{
var formFields = new BsonArray();
formFields.Add(new BsonDocument
{
{"ID", ObjectId.GenerateNewId()},
{"NAME", item["Name"].AsString}, // grabbed a few values from doc
{"TYPE", item["Type"].AsString} // to move into an array
});
collection.Save(item.Add("fields", formFields));
}
Test:
> db.so.remove()
> db.so.insert({_id: "12345", Name: "Jon Smith", Type: "Employee"})
> db.so.find()
{ "_id" : "12345", "Name" : "Jon Smith", "Type" : "Employee" }
> // Ran application here
> db.so.find()
{ "_id" : "12345", "Name" : "Jon Smith", "Type" : "Employee",
"fields" : [{"ID" : ObjectId("52039e395bddbf23f8cc0888"),
"NAME" : "Jon Smith",
"TYPE" : "Employee" } ] }
FYI: RequestStart returns an IDisposable object. I'm not sure why you're trying to use it, but you're using it incorrectly.

Categories

Resources