Trying to get a query that has both AND and OR to work in C#.
In SQL Server would be something like:
... where (Code = 'abc' OR Description = 'def') AND (Flag = 0)
With MongoVUE I seemed to have gotten it to work with this:
{ "$or" : [{ "Description" : /def/i }, { "Code" : /abc/i }], "$and": [{ "Flag" : 0 }] }
but can't seem to get it with the C#:
tried this:
List<IMongoQuery> qryValue = new List<IMongoQuery>();
qryValue.Add(Query.EQ("Code", "abc"));
qryValue.Add(Query.EQ("Description", "def"));
qryValue.Add(Query.And(Query.EQ("Flag", 1)));
var query = Query.Or(qryValue.ToArray());
but get this back:
{ "$or" : [{ "Code" : "abc" }, { "Description" : "def" }, { "Flag" : 1 }] }
and this doesn't give the correct results: missing the AND part.
Anyone can help with this?
You're getting that back because you're are putting all of your Query.EQ into the Query.Or on the last line. Passing in a single value to a Query.And doesn't do anything; it's the same as if you passed a single value to any other method. You haven't and-ed it with anything.
You need to write it in code as you have above in SQL: respect the parenthesis.
Group your Or together first, and pass it as the first parameter to the And, followed by the single, additional clause for Flag.
var clauses = new[] { Query.EQ("Code", "abc"), Query.EQ("Description", "def") };
var query = Query.AND(Query.OR(clauses), Query.EQ("Flag", 1))
Something like that should work.
Related
at the moment my notification documents has an events property which is an array of event. Each event has a status and a date. When querying notifications, it needs to check if the top status is opened.
Valid object where most recent event status is opened -
{
"subject" : "Hello there",
"events" : [
{
"status" : "opened",
"date" : 2020-01-02 17:35:31.229Z
},
{
"status" : "clicked",
"date" : 2020-01-01 17:35:31.229Z
},
]
}
Invalid object where status isn't most recent
{
"subject" : "Hello there",
"events" : [
{
"status" : "opened",
"date" : 2020-01-01 17:35:31.229Z
},
{
"status" : "clicked",
"date" : 2020-01-02 17:35:31.229Z
},
]
}
At the moment I have the query that can check if any event has the status opened, but I'm unsure how to query only the top 1 and sorted by the dates of a nested query. Any help would be greatly appreciated.
var filter = Builders<Notification>.Filter.Empty;
filter &= Builders<Notification>.Filter.Regex("events.event", new BsonRegularExpression(searchString, "i"));
var results = await collection.FindSync(filter, findOptions).ToListAsync();
In order to get only the latest event you can use $reduce to iterate over the events and compare each one to the temporarily latest:
db.collection.aggregate([
{
$addFields: {
latestEvent: {
$reduce: {
input: "$events",
initialValue: {status: null, date: 0},
in: {
$mergeObjects: [
"$$value",
{
$cond: [
{
$gt: [{$toDate: "$$this.date"}, {$toDate: "$$this.value"}]
},
"$$this",
"$$value"
]
}
]
}
}
}
}
}
])
See how it works on the playground example
for multiple documents, the result return only correct documents
example
db.collection.aggregate([{
$addFields: {
lastevent: {
$filter: {
input: '$events',
as: 'element',
cond: {$eq: ['$$element.date',{$max: '$events.date'}]}
}
}
}
}, {
$match: {
'lastevent.status': 'opened'
}
}])
I am a fan of not using an axe for everything, even if it is a good one :)
So i take it the events being disorderly is a rare thing, so we don't need to spend a lot of resources to weed out those up front as they will be few.
So my take is to get all the opened ones and use simple .net iteration to remove the few that may be, leaving a nice and orderly and easily maintainable method.
public List<Notification> GetValidSubjectStatusList(IMongoCollection<Notification> mongoCollection){
var builder = Builders<Notification>.Filter;
var filter = builder.Eq(x => x.Events.FirstOrDefault().Status, "opened");
var listOf = mongoCollection.Find(filter).ToList();
var reducedList = new List<Notification>();
foreach(var hit in listOf){
if(hit.Events.Any()
&& hit.Events.First()
.Date.Equals(hit.Events
.OrderByDescending(x => x.Date)
.FirstOrDefault()
))
{
reducedList.Add(hit);
}
}
return reducedList;
}
I've created my MongoDB documents below with subdocuments/arrays, however the arrays aren't named and I would like to delete the whole subdocument if a match is found on an elements within a subdocument.
For example, if a match is found on userID and userLogs.Name. My query is deleting the whole documents instead of only the userLog array. I've also tried other methods such as Pull and PullFilter whilst researching this topic but it doesn't seem to work with this structure, please can you advise on whether there is a way or if I will have to change my document structure?
Document
{
"_id" : ObjectId("43535"),
"userID" : "1",
"userLogs" : [
{
"logID" : 1,
"Name" : "Book 1",
"Genre" : "Fiction",
....
},
{
"logID" : 2,
"Name" : "Book 2",
"Genre" : "Non-Fiction",
....
}
]
}
C# Code behind
var collection = db.GetCollection<BsonDocument>("Users");
var arrayFilter = Builders<BsonDocument>.Filter.Eq("userID", uID) &
Builders<BsonDocument>.Filter.Eq("userLogs.Name", name);
collection.DeleteOne(arrayFilter);
Thank you Christoph! I also solved this using the following method:
var query = Builders<BsonDocument>.Filter.Eq("UserID", uID) &
Builders<BsonDocument>.Filter.Eq("userLogs.Name", name);
var update = Builders<BsonDocument>.Update.Pull("userLogs", new BsonDocument(){
{ "Name", name }
});
collection.UpdateOne(query, update);
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.
I have a document like this:
{ "File" : "xxxxxxx.txt",
"Content" : [
{ "tag" : "Book",
"name" : "TestBook1",
"value" : "xxx"
},
{ "tag" : "Dept",
"name" : "TestDept1",
"value" : "yyy"
},
{ "tag" : "Employee",
"name" : "TestEmployee1",
"value" : "zzz"
}]
}
I can find the document by using:
var filter = Builders<BsonDocument>.Filter.Eq("Content.tag", "Dept");
var result = collection.Find(filter).ToList();
However, this returns the whole document. Is there any way to just get the JSON object ({"tag" : "Dept", "name" : "TestDept1"})?
What I'm trying to get is just the "value" property (in this case, it's "yyy"), instead of the whole document.
UPDATE:
Like Phani suggested, I was able to make this work with the following code:
var subFilter = Builders<BsonDocument>.Filter.Eq("tag", "Dept");
var filter = Builders<BsonDocument>.Filter.ElemMatch("Content", subFilter);
var result =
collection.Find(filter)
.Project(Builders<BsonDocument>.Projection.Exclude("_id").Include("Content.$"))
.ToList();
You need to use the ElemMatch projection for this.
Shell query for this : db.testing.find({Content:{$elemMatch:{"tag":"Dept"}}},{"_id":0,"Content.$":1})
C# query will be
Find(x => x.Content.Any(p => p.tag == "Dept")).Project(Builders<BsonDocument>.Projection.Exclude("_id").Include("Content.$")).ToList();
Please check if this is working.
I have a "Payee" BsonDocument like this:
{
"Token" : "0b21ae960f25c6357286ce6c206bdef2",
"LastAccessed" : ISODate("2012-07-11T02:14:59.94Z"),
"Firstname" : "John",
"Lastname" : "Smith",
"PayrollInfo" : [{
"Tag" : "EARNINGS",
"Value" : "744.11",
}, {
"Tag" : "DEDUCTIONS",
"Value" : "70.01",
}],
},
"Status" : "1",
"_id" : ObjectId("4fc263158db2b88f762f1aa5")
}
I retrieve this document based on the Payee _id.
var collection = database.GetCollection("Payee");
var query = Query.EQ("_id", _id);
var bDoc = collection.FindOne(query);
Then, at various times I need to update a specific object inside the PayrollInfo array. So I search for the object with appropriate "Tag" inside the array and update the "Value" into the database. I use the following logic to do this:
var bsonPayrollInfo = bDoc["PayrollInfo", null];
if (bsonPayrollInfo != null)
{
var ArrayOfPayrollInfoObjects = bsonPayrollInfo.AsBsonArray;
for (int i = 0; i < ArrayOfPayrollInfoObjects.Count; i++)
{
var bInnerDoc = ArrayOfPayrollInfoObjects[i].AsBsonDocument;
if (bInnerDoc != null)
{
if (bInnerDoc["Tag"] == "EARNINGS")
{
//update here
var update = Update
.Set("PayrollInfo."+ i.ToString() + ".Value", 744.11)
collection.FindAndModify(query, null, update);
bUpdateData = true;
break;
}
}
}
}
if (!bUpdateData)
{
//Use Update.Push. This works fine and is not relevant to the question.
}
All this code works fine, but I think I am being cumbersome in achieving the result. Is there a more concise way of doing this? Essentially, I am trying to find a better way of updating an object inside of an array in a BsonDocument.
Mongo has a positional operator that will let you operate on the matched value in an array. The syntax is: field1.$.field2
Here's an example of how you'd use it from the Mongo shell:
db.dots.insert({tags: [{name: "beer", count: 2}, {name: "nuts", count: 3}]})
db.dots.update({"tags.name": "beer"}, {$inc: {"tags.$.count" : 1}})
result = db.dots.findOne()
{ "_id" : ObjectId("50078284ea80325278ff0c63"), "tags" : [ { "name" : "beer", "count" : 3 }, { "name" : "nuts", "count" : 3 } ] }
Putting my answer here in case it helps you. Based on #MrKurt's answer (thank you!), here is what I did to rework the code.
var collection = database.GetCollection("Payee");
var query = Query.EQ("_id", _id);
if (collection.Count(query) > 0)
{
//Found the Payee. Let's save his/her Tag for EARNINGS
UpdateBuilder update = null;
//Check if this Payee already has any EARNINGS Info saved.
//If so, we need to update that.
query = Query.And(query,
Query.EQ("PayrollInfo.Tag", "EARNINGS"));
//Update will be written based on whether we find the Tag:EARNINGS element in the PayrollInfo array
if (collection.Count(query) > 0)
{
//There is already an element in the PayrollInfo for EARNINGS
//Just update that element
update = Update
.Set("PayrollInfo.$.Value", "744.11");
}
else
{
//This user does not have any prior EARNINGS data. Add it to the user record
query = Query.EQ("_id", _id);
//Add a new element in the Array for PayrollInfo to store the EARNINGS data
update = Update.Push("PayrollInfo",
new BsonDocument {{"Tag", "EARNINGS"}, {"Value", "744.11"}}
);
}
//Run the update
collection.FindAndModify(query, null, update);
}
It doesn't look any lesser than my original code, but it is much more intuitive, and I got to learn a lot about positional operators!