Mongodb - How can I write a Push Upsert in c#? - c#

I would like to implement the following command through c#. I've seen the Update.PushAll command but I'm not sure it's the right way. Any suggestion?
db.students.update(
{ name: "joe" },
{ $push: { scores: { $each: [ 90, 92, 85 ] } } }
, upsert = true
)

You can use PushAllWrapped to add an array of scores to your existing document:
var collection = db.GetCollection<Student>("students");
var query = Query<Product>.EQ(p => p.Name, "joe");
var push = Update<Student>.PushAllWrapped<int>(p => p.Scores, newScores);
collection.Update(query, push, UpdateFlags.Multi);
Using the new syntax, you can achieve using PushEach:
var collection = db.GetCollection<Student>("students");
var filter = Builders<Students>.Filter.Eq("name", "joe");
var update = Builders<Students>.Update.PushEach<Score>(x=>x.Scores, scores);
await collection.UpdateOneAsync(filter, update);

Related

How check value existence in a deeply nested array in MongoDB.Driver C#?

As the following image, the collection is Posts. The _ids for the post and the comment are known.
So, How to check if a given value d exists in the AgreedUserIds?
Try this for single character search:
db.Posts.find(
{
_id: "b4d5...ff79",
Comments: {
$elemMatch: {
_id: "c1ea...d45",
AgreedUserIds: "d"
}
}
}
);
And this for multi character search:
db.Posts.find(
{
_id: "b4d5...ff79",
Comments: {
$elemMatch: {
_id: "c1ea...d45",
AgreedUserIds: { $in: ["c", "d"] }
}
}
}
);
If you want to keep it BsonDocument you have options to combine filters like this>
var client = new MongoClient();
var database = client.GetDatabase("test");
var collection = database.GetCollection<BsonDocument>("posts");
var postId = new BsonObjectId(new ObjectId("604b5ff389ff6887d1b91a93"));
var commentId = new BsonObjectId(new ObjectId("604b5ff389ff6887d1b91a92"));
var userId = "a";
//var userId = "e"; // not found
var postIdMatches = Builders<BsonDocument>.Filter.Eq("_id", postId);
var commentIdMatches = Builders<BsonDocument>.Filter.Eq("_id", commentId);
var userIdInside = Builders<BsonDocument>.Filter.AnyEq("AgreedUserIds", userId); // "a" in checking
var commentCheck = Builders<BsonDocument>.Filter.ElemMatch("Comments", userIdInside & commentIdMatches); // & combines filters. Also ElemMatch on any of the Comments is passing the criteria
var rows = (await collection.FindAsync<BsonDocument>(postIdMatches & commentCheck)).ToList();
You could also GetCollection<Post> with C# Post class describing the Post business object, and use .Any Linq query on that.

MongoDb - cursor option is required after upgrade of mongodb

Since we were forced to upgrade our mongo installation, we're receiving an error during some aggregation function calls:
MongoDB.Driver.MongoCommandException: "Command 'aggregate' failed: The
'cursor' option is required, except for aggregate with the explain
argument (response: { "ok" : 0.0, "errmsg" : "The 'cursor' option is
required, except for aggregate with the explain argument", "code" : 9,
"codeName" : "FailedToParse" })"
BsonArray arr = BsonSerializer.Deserialize<BsonArray>("[{ \"$match\" : { \"Param1\" : \"VAL\" } }, { \"$unwind\" : \"$Entries\" }, { \"$match\" : { \"PARAM\" : \"VALUE\" } }]");
var pipeline = arr.Select(x => x.AsBsonDocument).ToList();
// AggregateArgs aArgs = new AggregateArgs { Pipeline = bsonList };
var cursor = collection.Aggregate(pipeline).ResultDocuments;
I already figured out, that we have to manually add cursor configuration to the BsonDocument - but we weren't able to figure out, how the query should be configured.
Is there any work around for this exception (without changing drivers)?
give this a shot:
var cursor = collection.Aggregate<BsonDocument>(pipeline);
var results = cursor.ToList(); //just get a list of documents and be done with it
while (cursor.MoveNext()) // or iterate over cursor
{
foreach (var doc in cursor.Current.ToArray())
{
//access your documents here
}
}
You have extra brace in the end of query string
was finally able to fix it, by building the command by myself:
var cmd = new CommandDocument()
{
{"aggregate", "collection_name" },
{"pipeline", arr},
{"cursor", BsonDocument.Parse("{}") }
};
var res = db.RunCommand(cmd);
This is what worked in my situation (mongocshardriver v1.9.0-rc0, mongodb server 4.4.0); OutputMode = AggregateOutputMode.Cursor in the AggregateArgs.
public IEnumerable<BsonDocument> Run(MongoCollection<Item> items)
{
var priceRange = new BsonDocument(
"$subtract",
new BsonArray
{
"$Price",
new BsonDocument(
"$mod",
new BsonArray{"$Price", 100})
});
var grouping = new BsonDocument(
"$group",
new BsonDocument
{
{"_id", priceRange},
{"count", new BsonDocument("$sum", 1)}
});
var sort = new BsonDocument(
"$sort",
new BsonDocument("_id", 1)
);
var args = new AggregateArgs
{
Pipeline = new[] { grouping, sort },
OutputMode = AggregateOutputMode.Cursor,
};
return items.Aggregate(args);
}

MongoDB Update nested Array object where there is a match in array document

Here is my MongoDB Document
{
"_id":20,
"GroupId":"45",
"Name":"Some Name",
"NestedArray":[
{
"Id":3,
"Name":"NesName",
"IsDeleted":false
}
]
}
I need to write a update statement like (in SQL interpretation)
update MyCollections.NestedArray set MyCollections.NestedArray[x].IsDeleted = true where MyCollections.NestedArray[x].Id = 3
Here is what I tried
var groupFilter = Builders<MyType>.Filter.Eq(x => x.Id, 45);
var nestedArayDocUpdate = Builders<MyType>.Update.Set(x => x.NestedArray[0].IsDeleted, true);
mongoDbRepository.UpdateMany(groupFilter, nestedArayDocUpdate,
new UpdateOptions {IsUpsert = false, BypassDocumentValidation = false});
using MongoDB 3.2 how can I come up with a MongoDB C# driver query?
This is how I ended up doing it.
var updateBuilder = Builders<Type>.Update.
.Set(x => x.NestedArray[-1].IsActive, false)
.Set(x => x.NestedArray[-1].IsDeleted, true);
mongoDbRepository.UpdateOne( Builders<Type>.Filter.Where(
x => x.NestedArray.Any(c => c.Id == categoryId)), updateBuilder);

MongoDB aggregation Shell script to MongoC# Driver

How can I convert this Mongo Shell script to MongoDB C# Driver?
var myItems = []
var myCursor = db.Tickets.aggregate(
[
{ $match : { TicketProjectID : 49 } },
{ $project: { TicketProjectID:1, TicketID:1, concatValue: { $concat: [ "$Status", " - ", "$Name" ] } } }
// I will have a list of fields that I need to concatenate at run time. So C# query should support concatenation for "N" number of fields at run-time.
//{ $group: { _id: null, count: { $sum: 1 } } }
],
{ allowDiskUse: true }
)
//This seems like a ugly performance approach when we are working against 100k results with above match
while (myCursor.hasNext()) {
var item = myCursor.next();
if(item.concatValue.search(/mysearchkey/i) > -1)
{
myItems.push(item.TicketID)
}
}
myItems
or is there a better way to do the string search in concatenated projection instead of foreach in cursor, as some quires might get 50k records.
This is what I have tried so far, (Not using Aggregation)
Note: Trimmed this code to suite for public Q&A sites. So please consider this as Pseudo-code
var tickets = ticketsCollection.FindSync(filter).ToList();
string concatinatedValue = string.Empty;
foreach (var ticket in tickets)
{
foreach (var field in customFieldsForThisProject)
concatinatedValue += ticket[field.Replace(" ", "_")];
if(concatinatedValue.StripHtml().contains("MysearchWord"))
{
TikectIdList.Add(ticket["TicketID"])
}
}
Thanks to #Nikola.Lukovic, working on his pseudo-code, I came up with this working solution.
Approach one: fully using C# Driver
var ticketsCollection = _mongoConnect.Database.GetCollection<BsonDocument>("Tickets");
var dbResult = from ticket in ticketsCollection.AsQueryable()
select new
{
TicketProjectID = ticket["TicketProjectID"],
TicketID = ticket["TicketID"],
ConcatValue = ticket["Status"] + (string) ticket["Name"]
};
var matches = from dbr in dbResult
where dbr.ConcatValue.Contains(searchKey)
where dbr.ConcatValue.StartsWith(searchKey)
select dbr;
This will not work for my scenario as fields I am trying to
concatenate are if type string, but $add will only work with
numeric and date types.
Approach two: using RunCommand and passing straight Shell command. This will work for all datatypes. And works for my need as well.
var projectCommand =
BsonDocument.Parse(
"{ $project: { _id: -1, TicketProjectID:1, TicketID:1, concatValue: { $concat: [ \"$Status\", \" - \", \"$Name\" ] } } }");
var matchCommand =
BsonDocument.Parse("{ $match: {concatValue: { $regex: '" + searchKey + "', $options: 'i'} } }");
var pipeline = new[] {projectCommand, matchCommand};
var result = ticketsCollection.Aggregate<BsonDocument>(pipeline).ToList();
if (result.Count > 0)
return result.Select(x => (int)x["TicketID"]).ToList();
return null;
Edited according to the given comment
If you can use AsQueryable() you can get the values like this:
var dbResult = from ticket in ticketsCollection.AsQueryable()
where ticket.TicketProjectID == 49
select new
{
TicketProjectID = ticket.TicketProjectID,
TicketID = ticket.TicketID,
ConcatValue = ticket.Status + " - " + ticket.Name
};
and than later you can do something like this:
var result = from dbr in dbResult
where dbr.ConcatValue.Contains("something") //or
where dbr.ConcatValue.StartsWith("something")//or you can use regex
select dbr;
Note: For some reason both Status and Name properties from type Ticket need to be of a type String for concatenation to work since mongo driver won't recognize the call to ToString() from some other type.
If you want to concatenate properties from some other types you could get them separately from the db and than concat them locally.
note, i'm not that good with mongo shell i could mess something up but you can see in which way you could go
Alternatively you could write your shell command like this and put it in a string:
var command = #"db.Tickets.aggregate(
[
{ $project: { TicketProjectID:1, TicketID:1, concatValue: { $concat: [ "$Status", " - ", "$Name" ] } } },
{ $match : { TicketProjectId : 49, concatValue : { $regex : /mysearchkey/i } } }
],
{ allowDiskUse : true }
);";
then execute it in c# with RunCommandAsync method from MongoDatabase.
var result = await mongoDatabase.RunCommandAsync<BsonDocument>(BsonDocument.Parse(command));

Find in subdocument MongoDb

I need to first find the document by _id.
Then in the document to find subdocument which have a Time field is greater than the parameter lastTime
var filter = builder.Eq("_id", symbol) & builder.Gt("Update.Time", lastTime);
var result = await MongoDb.CollectionName.Find(filter).ToListAsync();
This example has a result of 0.
How to write this query? I need get this subdocument "Update" or last 3 sub-subdocument
The document has the following structure
{
{"_id", symbol},
{"Update", [
{"_id", number}, {"Time", sometime}, {"Version", versionNumber},
{"_id", number}, {"Time", sometime}, {"Version", versionNumber},
{"_id", number}, {"Time", sometime}, {"Version", versionNumber},
{"_id", number}, {"Time", sometime}, {"Version", versionNumber},}
]
}
If mongodb console will work an example:
> db.doc.save({
"_id":"123",
"update":[
{"_id":'1', "time":'345', "Version":'3'},
{"_id":'2', "time":'234', "Version":'4'}
]
})
> db.doc.findOne( { _id:"123", "update.time":{ $gt: 344 } } )
Then for C# Will can you to try ?
var collection = _database.GetCollection<BsonDocument>("name");
var filter = Builders<BsonDocument>.Filter.Gt("update.time", 344);
var result = await collection.Find(filter).ToListAsync();
This example
UPD This can be:
var collection = _database.GetCollection<BsonDocument>("name");
var builder = Builders<BsonDocument>.Filter;
var filter = builder.Eq("_id", id) & builder.Gt("update.time", 344);
var result = await collection.Find(filter).ToListAsync();
Try using lambda expressions:
var lastTime = 1;
var result = collection.Find(x => x.Update.Contains(lastTime));

Categories

Resources