Below is working code for 'And' Operation of Multiple Queries, I can able to do 'And' operation for list of Queries, 'Or' operation for list of Queries. But is there a way in MongoDB that i can do 'And' or 'Or' operation Dynamically for list of Queries?
public IQueryable<SocialRecord> GetWallSocialRecordsMongoQuery(Dictionary<string, List<string>> languagesPerTerms, string[] sources, DateTime fr, DateTime to)
{
try
{
var andList = new BindingList<IMongoQuery>
{
Query.And(Query<SocialRecord>.GTE(record => record.DateCreated, fr),
Query<SocialRecord>.LTE(record => record.DateCreated, to),
Query<SocialRecord>.In(record => record.SocialType, sources))
};
foreach (var languagesPerTerm in languagesPerTerms)
{
var term = languagesPerTerm;
andList.Add(Query.Or(Query.And((Query<SocialRecord>.Where(record => record.TermMonitorIds.Contains(term.Key))),
Query<SocialRecord>.In(record => record.Language, term.Value))));
}
return _collection.FindAs<SocialRecord>(Query.And(andList)).AsQueryable();
}
catch (Exception ex)
{
Log.Error("Exception in the Method GetWallSocialRecords, in the Class MongoSocialRecordRepository", ex);
}
return null;
}
Document Structure
{
"_id" : ObjectId("53a456b27f781d19f40ac76c"),
"DateCreated" : ISODate("2014-06-20T15:35:56.000Z"),
"SocialType" : "facebook",
"RecordId" : "1474971936_10202431655820767",
"UserId" : "1474971936",
"UserProfileUrl" : "********",
"UserProfilePictureUrl" : "/Downloads/v3/432bfeb8-901e-45a4-b739-1f3f48b69d61/facebook/2014-6/1946689/10492432_10202426005479512_740185019259071925_t.jpg",
"Description" : "******",
"MediaHiResUrl" : "",
"MediaLowResUrl" : "",
"MediaMedResUrl" : "",
"SocialCount" : NumberLong(354),
"SocialCountType" : "likes",
"Sentiment" : "",
"SentimentScore" : "0.0000000",
"IsLocalContent" : true,
"IsExactMatch" : true,
"IsHashTag" : false,
"IsActive" : false,
"MediaType" : "image",
"TermMonitorIds" : [
"432bfeb8-901e-45a4-b739-1f3f48b69d61"
],
"UserName" : "***",
"DisplayName" : "",
"DirectUrl" : "",
"IsUk" : true,
"IsEnglish" : true,
"Language" : "en",
"Location" : "GB",
"DataVersion" : "v3"
}
Edit
I need to get below Query:
query {
"DateCreated" : {
"$gte" : ISODate("2015-02-02T16:55:37.979Z"),
"$lte" : ISODate("2015-02-09T16:55:37.979Z")
},
"SocialType" : {
"$in" : ["twitter"]
},
"$or" : [{
"TermMonitorIds" : "b34b8bea-d1e6-4d05-bd25-5d07ad0b691e",
"Language" : {
"$in" : ["zh", "eo", "ja"]
}
}, {
"TermMonitorIds" : "c8497f52-70dd-47b6-8abe-afac42c3a009",
"Language" : {
"$in" : ["zh", "eo", "ja"]
}
}
]
}
Any suggestions will be appreciated.
I found a way to do that, Might help anyone :
public IQueryable<SocialRecord> GetWallSocialRecordsMongoQuery(Dictionary<string, List<string>> languagesPerTerms, string[] sources, DateTime fr, DateTime to)
{
try
{
var list = new BindingList<IMongoQuery>();
foreach (var languagesPerTerm in languagesPerTerms)
{
var term = languagesPerTerm;
list.Add(!term.Value.Contains("All")
? Query.And(
Query<SocialRecord>.Where(record => record.TermMonitorIds.Contains(term.Key)),
Query<SocialRecord>.In(record => record.Language, term.Value))
: Query<SocialRecord>.Where(record => record.TermMonitorIds.Contains(term.Key)));
}
var query = Query.And(Query<SocialRecord>.GTE(record => record.DateCreated, fr),
Query<SocialRecord>.LTE(record => record.DateCreated, to),
Query<SocialRecord>.In(record => record.SocialType, sources),
Query.Or(list));
//var x = _collection.FindAs<SocialRecord>(query).Explain();
return _collection.FindAs<SocialRecord>(query).AsQueryable();
}
catch (Exception ex)
{
Log.Error("Exception in the Method GetWallSocialRecords, in the Class MongoSocialRecordRepository", ex);
}
return null;
}
Please not that MongoDB Queries with the $or operator can use separate indexes on each clause of the $or expression.
Related
I have the following MongoDb query working:
db.Entity.aggregate(
[
{
"$match":{"Id": "12345"}
},
{
"$lookup": {
"from": "OtherCollection",
"localField": "otherCollectionId",
"foreignField": "Id",
"as": "ent"
}
},
{
"$project": {
"Name": 1,
"Date": 1,
"OtherObject": { "$arrayElemAt": [ "$ent", 0 ] }
}
},
{
"$sort": {
"OtherObject.Profile.Name": 1
}
}
]
)
This retrieves a list of objects joined with a matching object from another collection.
Does anybody know how I can use this in C# using either LINQ or by using this exact string?
I tried using the following code but it can't seem to find the types for QueryDocument and MongoCursor - I think they've been deprecated?
BsonDocument document = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<BsonDocument>("{ name : value }");
QueryDocument queryDoc = new QueryDocument(document);
MongoCursor toReturn = _connectionCollection.Find(queryDoc);
There is no need to parse the JSON. Everything here can actually be done directly with either LINQ or the Aggregate Fluent interfaces.
Just using some demonstration classes because the question does not really give much to go on.
Setup
Basically we have two collections here, being
entities
{ "_id" : ObjectId("5b08ceb40a8a7614c70a5710"), "name" : "A" }
{ "_id" : ObjectId("5b08ceb40a8a7614c70a5711"), "name" : "B" }
and others
{
"_id" : ObjectId("5b08cef10a8a7614c70a5712"),
"entity" : ObjectId("5b08ceb40a8a7614c70a5710"),
"name" : "Sub-A"
}
{
"_id" : ObjectId("5b08cefd0a8a7614c70a5713"),
"entity" : ObjectId("5b08ceb40a8a7614c70a5711"),
"name" : "Sub-B"
}
And a couple of classes to bind them to, just as very basic examples:
public class Entity
{
public ObjectId id;
public string name { get; set; }
}
public class Other
{
public ObjectId id;
public ObjectId entity { get; set; }
public string name { get; set; }
}
public class EntityWithOthers
{
public ObjectId id;
public string name { get; set; }
public IEnumerable<Other> others;
}
public class EntityWithOther
{
public ObjectId id;
public string name { get; set; }
public Other others;
}
Queries
Fluent Interface
var listNames = new[] { "A", "B" };
var query = entities.Aggregate()
.Match(p => listNames.Contains(p.name))
.Lookup(
foreignCollection: others,
localField: e => e.id,
foreignField: f => f.entity,
#as: (EntityWithOthers eo) => eo.others
)
.Project(p => new { p.id, p.name, other = p.others.First() } )
.Sort(new BsonDocument("other.name",-1))
.ToList();
Request sent to server:
[
{ "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
{ "$lookup" : {
"from" : "others",
"localField" : "_id",
"foreignField" : "entity",
"as" : "others"
} },
{ "$project" : {
"id" : "$_id",
"name" : "$name",
"other" : { "$arrayElemAt" : [ "$others", 0 ] },
"_id" : 0
} },
{ "$sort" : { "other.name" : -1 } }
]
Probably the easiest to understand since the fluent interface is basically the same as the general BSON structure. The $lookup stage has all the same arguments and the $arrayElemAt is represented with First(). For the $sort you can simply supply a BSON document or other valid expression.
An alternate is the newer expressive form of $lookup with a sub-pipeline statement for MongoDB 3.6 and above.
BsonArray subpipeline = new BsonArray();
subpipeline.Add(
new BsonDocument("$match",new BsonDocument(
"$expr", new BsonDocument(
"$eq", new BsonArray { "$$entity", "$entity" }
)
))
);
var lookup = new BsonDocument("$lookup",
new BsonDocument("from", "others")
.Add("let", new BsonDocument("entity", "$_id"))
.Add("pipeline", subpipeline)
.Add("as","others")
);
var query = entities.Aggregate()
.Match(p => listNames.Contains(p.name))
.AppendStage<EntityWithOthers>(lookup)
.Unwind<EntityWithOthers, EntityWithOther>(p => p.others)
.SortByDescending(p => p.others.name)
.ToList();
Request sent to server:
[
{ "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
{ "$lookup" : {
"from" : "others",
"let" : { "entity" : "$_id" },
"pipeline" : [
{ "$match" : { "$expr" : { "$eq" : [ "$$entity", "$entity" ] } } }
],
"as" : "others"
} },
{ "$unwind" : "$others" },
{ "$sort" : { "others.name" : -1 } }
]
The Fluent "Builder" does not support the syntax directly yet, nor do LINQ Expressions support the $expr operator, however you can still construct using BsonDocument and BsonArray or other valid expressions. Here we also "type" the $unwind result in order to apply a $sort using an expression rather than a BsonDocument as shown earlier.
Aside from other uses, a primary task of a "sub-pipeline" is to reduce the documents returned in the target array of $lookup. Also the $unwind here serves a purpose of actually being "merged" into the $lookup statement on server execution, so this is typically more efficient than just grabbing the first element of the resulting array.
Queryable GroupJoin
var query = entities.AsQueryable()
.Where(p => listNames.Contains(p.name))
.GroupJoin(
others.AsQueryable(),
p => p.id,
o => o.entity,
(p, o) => new { p.id, p.name, other = o.First() }
)
.OrderByDescending(p => p.other.name);
Request sent to server:
[
{ "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
{ "$lookup" : {
"from" : "others",
"localField" : "_id",
"foreignField" : "entity",
"as" : "o"
} },
{ "$project" : {
"id" : "$_id",
"name" : "$name",
"other" : { "$arrayElemAt" : [ "$o", 0 ] },
"_id" : 0
} },
{ "$sort" : { "other.name" : -1 } }
]
This is almost identical but just using the different interface and produces a slightly different BSON statement, and really only because of the simplified naming in the functional statements. This does bring up the other possibility of simply using an $unwind as produced from a SelectMany():
var query = entities.AsQueryable()
.Where(p => listNames.Contains(p.name))
.GroupJoin(
others.AsQueryable(),
p => p.id,
o => o.entity,
(p, o) => new { p.id, p.name, other = o }
)
.SelectMany(p => p.other, (p, other) => new { p.id, p.name, other })
.OrderByDescending(p => p.other.name);
Request sent to server:
[
{ "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
{ "$lookup" : {
"from" : "others",
"localField" : "_id",
"foreignField" : "entity",
"as" : "o"
}},
{ "$project" : {
"id" : "$_id",
"name" : "$name",
"other" : "$o",
"_id" : 0
} },
{ "$unwind" : "$other" },
{ "$project" : {
"id" : "$id",
"name" : "$name",
"other" : "$other",
"_id" : 0
}},
{ "$sort" : { "other.name" : -1 } }
]
Normally placing an $unwind directly following $lookup is actually an "optimized pattern" for the aggregation framework. However the .NET driver does mess this up in this combination by forcing a $project in between rather than using the implied naming on the "as". If not for that, this is actually better than the $arrayElemAt when you know you have "one" related result. If you want the $unwind "coalescence", then you are better off using the fluent interface, or a different form as demonstrated later.
Querable Natural
var query = from p in entities.AsQueryable()
where listNames.Contains(p.name)
join o in others.AsQueryable() on p.id equals o.entity into joined
select new { p.id, p.name, other = joined.First() }
into p
orderby p.other.name descending
select p;
Request sent to server:
[
{ "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
{ "$lookup" : {
"from" : "others",
"localField" : "_id",
"foreignField" : "entity",
"as" : "joined"
} },
{ "$project" : {
"id" : "$_id",
"name" : "$name",
"other" : { "$arrayElemAt" : [ "$joined", 0 ] },
"_id" : 0
} },
{ "$sort" : { "other.name" : -1 } }
]
All pretty familiar and really just down to functional naming. Just as with using the $unwind option:
var query = from p in entities.AsQueryable()
where listNames.Contains(p.name)
join o in others.AsQueryable() on p.id equals o.entity into joined
from sub_o in joined.DefaultIfEmpty()
select new { p.id, p.name, other = sub_o }
into p
orderby p.other.name descending
select p;
Request sent to server:
[
{ "$match" : { "name" : { "$in" : [ "A", "B" ] } } },
{ "$lookup" : {
"from" : "others",
"localField" : "_id",
"foreignField" : "entity",
"as" : "joined"
} },
{ "$unwind" : {
"path" : "$joined", "preserveNullAndEmptyArrays" : true
} },
{ "$project" : {
"id" : "$_id",
"name" : "$name",
"other" : "$joined",
"_id" : 0
} },
{ "$sort" : { "other.name" : -1 } }
]
Which actually is using the "optimized coalescence" form. The translator still insists on adding a $project since we need the intermediate select in order to make the statement valid.
Summary
So there are quite a few ways to essentially arrive at what is basically the same query statement with exactly the same results. Whilst you "could" parse the JSON to BsonDocument form and feed this to the fluent Aggregate() command, it's generally better to use the natural builders or the LINQ interfaces as they do easily map onto the same statement.
The options with $unwind are largely shown because even with a "singular" match that "coalescence" form is actually far more optimal then using $arrayElemAt to take the "first" array element. This even becomes more important with considerations of things like the BSON Limit where the $lookup target array could cause the parent document to exceed 16MB without further filtering. There is another post here on Aggregate $lookup Total size of documents in matching pipeline exceeds maximum document size where I actually discuss how to avoid that limit being hit by using such options or other Lookup() syntax available to the fluent interface only at this time.
I have a collection Zonedetails shown below. below.
I am using C# to Insert or Update a Unit to Units array. If it is an insert I can insert Area blank. If it is an update It should only update the UnitName.
{
"Code" : "Zone1",
"Name" : "ZoneName1",
"Units" : [
{
"UnitCode" : "Unitcode1",
"UnitName" : "UnitCodeName",
"Areas" : [
{
"AreaCode" : "AreaCode1",
"AreaName" : "AreaName1"
}
]
}
]
}
{
"Code" : "Zone2",
"Name" : "ZoneName2",
"Units" : [
{
"UnitCode" : "UnitCode2",
"UnitName" : "UnityName2",
"Areas" : [
{
"AreaCode" : "Areacode2",
"AreaName" : "AreaName2"
}
]
}
]
}
{
"Code" : "Zone3",
"Name" : "ZoneName3",
"Units" : [
{
"UnitCode" : "UnitCode3",
"UnitName" : "UnitName3",
"Areas" : [
{
"AreaCode" : "Areadcode3",
"AreaName" : "AreaName3"
},
{
"AreaCode" : "AreaCode4",
"AreaName" : "Areaname4"
},
{
"AreaCode" : "AreaCode5",
"AreaName" : "Areaname5"
}
]
},
{
"UnitCode" : "UnitCode6",
"UnitName" : "UnitName6",
"Areas" : [
{
"AreaCode" : "AreaCode10",
"AreaName" : "AreaName10"
},
{
"AreaCode" : "AreaCOde11",
"AreaName" : "AreaName10"
},
{
"AreaCode" : "AreaCode12",
"AreaName" : "AreaName12"
}
]
}
]
}
I have writtent a C# code shown below. But getting "The positional operator did not find the match needed from the query"error if the Unit Code does not exist. Added not before unitCode check.
var mongoCollection = _context.GetCollection<Zone>("ZoneDetail");
var filter = Builders<Zone>.Filter.Where(x => x.Code == zoneCode && !x.Units.Any(u => u.UnitCode == unit.UnitCode));
var updateUnitCode = Builders<Zone>.Update.Set(x => x.Units.ElementAt(-1).UnitCode, unit.UnitCode);
var updateUnitName = Builders<Zone>.Update.Set(x => x.Units.ElementAt(-1).UnitName, unit.UnitName);
var result = await mongoCollection.UpdateOneAsync(filter, Builders<Zone>.Update.Combine(updateUnitCode, updateUnitName), new UpdateOptions { IsUpsert = true});
Your error message suggest that filter is not able to find matching element.
Either try to change your input or change your filter criteria.
I have this query which works when I run it in Robo 3T:
db.getCollection('groupSchedule').aggregate([{ "$match" : { "GroupId" : ObjectId("598dd346e5549706a80680bf") } },
{ "$lookup" : { "from" : "schedule", "localField" : "ScheduleIds", "foreignField" : "_id", "as" : "Schedule" } },
{ "$unwind" : "$Schedule" },
{ "$match" : {"$or": [{ "Schedule.End.ByDate" : {"$gte":new Date()}},{ "Schedule.End.ByDate" : null}] } },
{ "$group" : { "_id" : "$GroupId", "SurveyIds" : { "$addToSet" : "$Schedule.SurveyId" }, "ScheduleIds" : { "$addToSet" : "$Schedule._id" } } },
{ "$project" : { "_id" : 0, "SurveyIds" : 1, "ScheduleIds": 1 } }])
However, when I try to do the same thing using the C# driver as it blows up saying that:
"Duplicate element name 'Schedule.End.ByDate'.",
Here's the code:
return new List<BsonDocument>
{
Common.Util.MongoUtils.Match(new BsonDocument { { "GroupId", groupId } }),
Common.Util.MongoUtils.Lookup(scheduleCollections, "ScheduleIds", "_id", "Schedule"),
Common.Util.MongoUtils.Unwind("$Schedule"),
Common.Util.MongoUtils.Match(new BsonDocument
{
{
"$or", new BsonDocument
{
{
"Schedule.End.ByDate", BsonNull.Value
},
{
"Schedule.End.ByDate", new BsonDocument
{
{
"$gte", DateTime.UtcNow
}
}
}
}
}
}),
Group(),
Common.Util.MongoUtils.Project(new BsonDocument
{
{ "_id", 0 },
{ "SurveyIds", 1 },
{ "Schedules", 1 }
})
};
Any thoughts?
By using BsonDocument for your $or operator, you're effectively trying to create the following:
"$or": {
"Schedule.End.ByDate": null,
"Schedule.End.ByDate": { "$gte" : ISODate("...") }
}
If we look again at your error message:
"Duplicate element name 'Schedule.End.ByDate'.",
It's clear that you have duplicated the Schedule.End.ByDate element name, which is invalid and not what was intended.
Instead, you want to use BsonArray to wrap two separate objects, in order to produce the result you have in your Robo 3T query. To achieve that, you can use the following adaptation of your C# code:
"$or", new BsonArray
{
new BsonDocument
{
{ "Schedule.End.ByDate", BsonNull.Value }
},
new BsonDocument
{
{
"Schedule.End.ByDate", new BsonDocument
{
{ "$gte", DateTime.UtcNow }
}
}
}
}
This produces the following, which matches your Robo 3T query for the $or section:
{ "$or" : [
{ "Schedule.End.ByDate" : null },
{ "Schedule.End.ByDate" : { "$gte" : ISODate("...") } }
] }
imagine this situation:
I have a entity called Filter with:
string Text { get; set; }
string State { get; set; }
So in my Mongo collection I have this documents (filters):
{
"id" : "1",
"State" : "333",
"Text" : "SD"
},
{
"id" : "2",
"State" : "444",
"Text" : "X"
}
Now I have a service that it is supposed to return all the documents with the match of texts and states.
So imagine my service receives a IEnumerable<string> texts and a IEnumerable<string> states with this values:
"states" : ["333","444"],
"texts" : ["SD"]
And the service should return [] because there aren't Filters with state "333" and state "444" with text "SD" at the same time. It only should return Results if my collection had this documents:
{
"id" : "1",
"State" : "333",
"Text" : "SD"
},
{
"id" : "2",
"State" : "444",
"Text" : "SD"
}
Then the response would be:
{
"id" : "1",
"State" : "333",
"Text" : "SD"
},
{
"id" : "2",
"State" : "444",
"Text" : "SD"
}
My actual code to create my Filter Definition for this situation is:
private FilterDefinition<MyFilterEntity> CreateFilterForMyFilterEntity(IEnumerable<string> texts, IEnumerable<string> states)
{
var filterBuilder = Builders<MyFilterEntity>.Filter;
var filter = filterBuilder.Empty;
if (ListNotNullOrEmpty(texts))
{
filter = filter & filterBuilder.In(rf => rf.Text, texts);
}
if (ListNotNullOrEmpty(states))
{
filter = filter & filterBuilder.In(rf => rf.State, states);
}
return filter;
}
But for my example case this code returns the response:
{
"id" : "1",
"State" : "333",
"Text" : "SD"
}
when it should be [] as I said before.
Is there a way to solve this situation? Thank you.
I am using ASP.NET Core 2.0 with MongoDb.Driver 2.5.0.
You can specify a list of expected filters, then use $or query like below and before you return results you need to check whether length of expected filters has the same value as length of returned results.
In MongoShell your query could look like this:
db.Filter.find(
{
$or: [
{ State: "333", Text: "SD" },
{ State: "444", Text: "SD" }
]
}
)
And your C# logic could be like below:
var states = new[] { "333", "444" };
var texts = new[] { "SD" };
var expectedNumberOfResults = states.Length * texts.Length;
IEnumerable<FilterClass> result;
var filters = from state in states
from text in texts
select Builders<FilterClass>.Filter.Where(f => f.Text == text && f.State == state);
FilterDefinition<FilterClass> q = Builders<FilterClass>.Filter.Or(filters);
var query = collection.Find(q);
if(query.Count() == expectedNumberOfResults)
{
result = query.ToList();
}
else
{
result = Enumerable.Empty<FilterClass>();
}
any one can help me,,
my brain stacking for make this collection to mapreduce,
how i can get PurchaseAmount, PurchaseReturnAmount, and TotalAmount, with key by "year".
{
"_id" : {
"OwnerId" : "coba#aja.com",
"SupplierId" : BinData(3,"AYC8In8bFkGYNx34poQLlg=="),
"GroupId" : BinData(3,"mzWBCilngEGd72YpeyijcQ=="),
"ProductId" : BinData(3,"gZ2g/syue06v8b88+0pqRA=="),
"Date" : ISODate("2013-01-15T00:00:00Z")
},
"value" : {
"OwnerId" : "oetawan#dokuku.com",
"Date" : ISODate("2013-01-15T00:00:00Z"),
"SupplierId" : BinData(3,"AYC8In8bFkGYNx34poQLlg=="),
"SupplierName" : "Matahari",
"GroupId" : BinData(3,"mzWBCilngEGd72YpeyijcQ=="),
"GroupCode" : "Umum",
"GroupName" : "Umum",
"ProductId" : BinData(3,"gZ2g/syue06v8b88+0pqRA=="),
"ProductCode" : null,
"ProductBarcode" : "IPAD2",
"ProductName" : "iPad 2",
"PurchaseAmount" : 19500000,
"PurchaseReturnAmount" : 0,
"TotalAmount" : 19500000
}
}
i want to use this map reduce to C#
Here's a shell example using MR. You could also do it with the aggregation framework of course, but presumably you want to run this in the background;
db.so.mapReduce(
function () { /* map */
var key = this._id.Date.getFullYear();
emit(key, { PurchaseAmount:this.value.PurchaseAmount, PurchaseReturnAmount:this.value.PurchaseReturnAmount, TotalAmount:this.value.TotalAmount } );
},
function (key, array) { /* reduce*/
var result = { PurchaseAmount: 0, PurchaseReturnAmount: 0, TotalAmount: 0 };
for (var i = 0; i < array.length; i++) {
{
result.PurchaseAmount += array[i].PurchaseAmount;
result.PurchaseReturnAmount += array[i].PurchaseReturnAmount;
result.TotalAmount += array[i].TotalAmount;
}
}
return result;
},
{
out: "so_mr_example",
query:{}
}
);
db.so_mr_example.find();
Produces something like;
{ "_id" : 2013, "value" : { "PurchaseAmount" : 39000000, "PurchaseReturnAmount" : 0, "TotalAmount" : 39000000 } }
{ "_id" : 2014, "value" : { "PurchaseAmount" : 19500000, "PurchaseReturnAmount" : 0, "TotalAmount" : 19500000 } }