this question refers to the example in How to project specific fields in array on filtered lookup
where a filter was applied on the 'joined' table.
Now I want to translate this into a query in C#, but I don't manage to add the filter to the projection. This is as far as I got:
db.GetCollection("meta")
.Aggregate()
.Match(new BsonDocument { { "test", "OK" }})
.Lookup("merge","Exp","Exp","kin")
.Project(Builders<BsonDocument>.Projection
.Include("Exp")
.Include("test")
.Include("kin")
)
Any idea?
It seems to work the hard way:
db.GetCollection("meta")
.Aggregate()
.Match(new BsonDocument { { "test", "OK" }})
.Lookup("merge","Exp","Exp","kin")
.Project(new BsonDocument {
{ "Exp" , 1},
{ "test" , 1},
{ "kin", new BsonDocument {
{ "$filter", new BsonDocument {
{ "input", "$kin"},
{ "as", , "kin"},
{ "cond", new BsonDocument {
{ "$eq", new BsonArray { "$$kin.M2", "val"}}}
}}
}}
}})
.Project(new BsonDocument {
{ "Exp", 1 },
{"test", 1},
{"date", 1},
{"kin.M1",1},
{"kin.M2",1},
{"kin.T",1 }})
but of course it would be nice to use the API of the Mongo Driver.
Related
Lets say I am having two collections named CollectionA and CollectionB, Both collection have different fields.
CollectionA will have multiple documents with Same field,
CollectionB contain only one document
Example
CollectionA
{
"UniqeId" :1,
"Hobbies" : "Eating"
},
{
"UniqeId" :2,
"Hobbies" : "Sleeping"
},
{
"UniqeId" :3,
"Hobbies" : "Walking"
}
CollectionB
{
"UserName" :"Sukuna",
"UserType" : "Villan"
}
I want output like this
{
"UniqeId" :1,
"Hobbies" : "Eating",
"UserName" :"Sukuna",
"UserType" : "Villan"
}
Consider All the documents in a CollectionA will contain same fields
And you can see there is no Unique fields between the two collection, and you can see we need to apply filter in CollectionA
ie) UniqeId=1
I am using C#, and I can able do two DB request to get those collection details (One req for CollectionA output and another one for CollectionB output) and manage to combine both in API level to get the desired output, but I want to do in DB level itself,
I don't want two DB calls, that is eating the API performance, so is there anyway to achieve this in a single DB call or by using any aggregate pipeline?.
Thanks in Advance
lookup with localField 1 and foreignField 1
db.a.aggregate([
{
$lookup: {
from: "b",
localField: "1",
foreignField: "1",
as: "docs"
}
},
{
$replaceRoot: {
newRoot: {
$mergeObjects: [
"$$ROOT",
{ $first: "$docs" }
]
}
}
},
{
$unset: [ "docs", "_id" ]
},
{
$group: {
_id: "$UserName",
doc: { $first: "$$ROOT" }
}
},
{
$replaceWith: "$doc"
}
])
mongoplayground
Finally after lots of trail and error and playing with pipeline, I can able to do that in aggregate pipeline, and I am using unionWith and group.
Here is the C# code
var pipeline1= new BsonDocument("$unionWith",
new BsonDocument
{
{ "coll", "CollectionB" },
{ "pipeline",
new BsonArray
{
new BsonDocument("$match",
new BsonDocument("UniqeId", 1))
} }
});
var pipeline2 = new BsonDocument("$group",
new BsonDocument
{
{ "_id", 0 },
{ "merged",
new BsonDocument("$push", "$$ROOT") }
});
var pipeline3 = new BsonDocument("$replaceRoot",
new BsonDocument("newRoot",
new BsonDocument("$mergeObjects", "$merged")));
var pipeline4 = new BsonDocument("$project",
new BsonDocument("_id", 0));
BsonDocument[] pipeline = new BsonDocument[] { pipeline1, pipeline2, pipeline3, pipeline4 };
var dbResponse = await collection.Aggregate<BsonDocument>(pipeline).ToListAsync();
I'm trying to deserialize filter with LUUID (or NUUID in this example) to BsonDocument:
var tmpQry = "{'ValueId': NUUID('ca7ac84f-18bf-42f0-b028-333ed144c549')";
var tmpBson = MongoDB.Bson.Serialization.BsonSerializer.Deserialize<BsonDocument>(tmpQry);
and getting an error:
System.FormatException: 'JSON reader was expecting a value but found 'NUUID'.'
I understand, that LUUID is not valid for JSON, but is it somehow possible to get BsonDocument from my string?
In my particular case i can't implement MongoDB nested $elemMatch query, like in this issue.
But my query contains identifiers:
db.getCollection('my_db').aggregate([
{
$match: {
'Event.key_attributes': {
$all: [
{ '$elemMatch': { 'Type.Code': 'code1', 'ValueId': LUUID('00000000-0000-0000-0000-000000000001') } },
{ '$elemMatch': { 'Type.Code': 'code2', 'ValueId': LUUID("00000000-0000-0000-0000-000000000002") } },
{ '$elemMatch': { 'Type.Code': 'code3', 'ValueId': LUUID("00000000-0000-0000-0000-000000000003") } }
]
}
}
},
{
$group: {
'_id': '$$CURRENT.Event.type._id',
'code': { '$last': '$$CURRENT.Event.type.Code' },
'value': { '$sum': '$$CURRENT.Event.value' }
}
}
]);
, and I even can't deserialize it into a BsonDocument.
Does my problem have any solution?
Thanks a lot.
Finally, I solved this issue. Instead of trying to serialize the query from a string, I created BsonDocument pipeline:
var filter = new BsonDocument {{
"$match", new BsonDocument {{
"Event.key_attributes", new BsonDocument {{
"$all", new BsonArray().AddRange(limit.KeyAttributes.Select(ka => new BsonDocument(
"$elemMatch", new BsonDocument().AddRange(new List<BsonElement>{
new BsonElement("Type.Code", ka.Type.Code),
new BsonElement("ValueId", ka.ValueId)
})
)).ToList())
}}
}}
}};
var group = new BsonDocument {{
"$group", new BsonDocument().AddRange(new List<BsonElement>{
new BsonElement("_id", "$$CURRENT.Event.type._id"),
new BsonElement("code", new BsonDocument{{
"$last", "$$CURRENT.Event.type.Code" }}),
new BsonElement("value", new BsonDocument{{
"$sum", "$$CURRENT.Event.value" }})
})
}};
var pipeline = new BsonDocument[]
{
filter,
group
};
var result = collection.Aggregate<MyOutputClass>(pipeline).ToListAsync();
There were no problems with Guid.
The poster has worked around his problem, but for those happening up this question later: Use CSUUID('guid-string-here') for the CSharp legacy format.
I need to run the following query in MongoDB in my C# code. I use MongoDB.Driver 2.7.2 and MongoDB 4.0.
I would like to use $lookup rather than $project / $unwind in order to prevent naming each one of the collection's possible fields.
db.getCollection('Collection1').aggregate([
{ "$match" : { "Id" : 1 } },
{ "$lookup": {
"from": "Collection2",
"let": { collection1Id : "$Id" },
"pipeline": [
{ $match:
{ $expr:
{ $and:
[
{ $eq : [ "$Collection1Id", "$$collection2Id" ] },
{ $in : [ "$_id" , [1, 2, 3]] }
]
}
}
}
],
"as": "item"
}
}
])
I expect the output of Collection1 joined with Collection2 under multiple join conditions.
As you didn't specify a particular class mapping and example documents, below answer will use BsonDocument type and example data from the manual $lookup: specify multiple join conditions with lookup
BsonArray subpipeline = new BsonArray();
subpipeline.Add(
new BsonDocument("$match",new BsonDocument(
"$expr", new BsonDocument(
"$and", new BsonArray {
new BsonDocument("$eq", new BsonArray{"$stock_item", "$$order_item"} ),
new BsonDocument("$gte", new BsonArray{"$instock", "$$order_qty"} )
}
)
))
);
var lookup = new BsonDocument("$lookup",
new BsonDocument("from", "warehouses")
.Add("let",
new BsonDocument("order_item", "$item")
.Add("order_qty", "$ordered"))
.Add("pipeline", subpipeline)
.Add("as", "stockdata")
);
var results = collection.Aggregate()
.Match(new BsonDocument("_id", 1))
.AppendStage<BsonDocument>(lookup).ToEnumerable();
foreach (var x in results)
{
Console.WriteLine(x.ToJson());
}
Please note that support for more expressive $lookup using PipelineDefinitionBuilder is coming for version 2.8.x. For more information see CSHARP-2013
I am trying to create a console application which will read through a Mongo db and filter out some data based on some business logic and display the results on console screen.
Till now I am able to write the following query and execute it successfully on Mongo shell. Here is the query:
{db.Collection.aggregate([
{$unwind: { path: "$Pages"}},
{$group : {
_id :{UrlPath: "$Pages.Url.Path", I_Id : "$_id", Date: { $dateToString: { format: "%Y-%m-%d",date: "$Pages.DateTime"}}, CId: "$CId",
x: {$sum:1},
y : {$sum:"$V"}
}},
{$group : {
_id : {Date: "$_id.Date",CId: "$_id.CId", PageUrl: "$_id.UrlPath"},
p: {$sum:1},
q : {$sum:"$x"},
TotalCount: {$sum:"$y"}
}},
{$sort:{p:-1}},
],{allowDiskUse: true}).pretty();
};
The problem I am facing is replicating the same query logic in C# code. Till now I able to connect to mongo db collections and able to basic CRUD operations.
But replicating this aggregate logic is really blowing my mind. I have tried using PipelineDefinition option but can't manage to get correct output out of it.
Can some one please guide me in the correct direction here.
Thanks in advance
Finally I was able to achieve it using collection.Aggregate() method.
Code looks like this:
collection.Aggregate(new AggregateOptions { AllowDiskUse = true})
.Unwind(i => i.Pages)
.Group(new BsonDocument
{
{
"_id", new BsonDocument
{
{ "Date", new BsonDocument("$dateToString", new BsonDocument {{"format", "%Y-%m-%d"}, {"date", "$Pages.DateTime"}})},
{"CId","$_id"}
}
},
{
"x", new BsonDocument
{
{"$sum", "$Value"}
}
}
}
)
.Group(new BsonDocument
{
{
"_id", new BsonDocument
{
{"Date", "$_id.Date"},
{"CId", "$_id.CId"}
}
},
{
"p", new BsonDocument
{
{"$sum", 1}
}
},
{
"q", new BsonDocument
{
{"$sum", "$x"}
}
}
}
)
.Sort(new BsonDocument("q", -1))
.ToList();
I am doing a NearSphere query with the C# MongoDB driver 2.0 and it works fine.
The results are ordered by distance automatically but I would like to get that distance back for each of the search results to be able to display it back.
I found this post that says how to do it for the old version of the driver Retrieving the Distance "dis" result from a Near query but didn't manage to find how to do it with the new drivers.
This is my code:
var collection = database.GetCollection<MyType>("myTypes");
var locFilter = Builders<MyType>.Filter.NearSphere(x => x.GeoLocation, criteria.Long, criteria.Lat, criteria.RadiusInMiles/3963.2);
var results = await collection.Find(locFilter).ToListAsync();
I guess I have to do something before calling ToList on the IFindFluent result ?
Any help ?
Thanks a lot
The C# MongoDB Driver lacks of some operations but the reality there's not any restriction querying the database. The methods Project, Match and so forth are just helpers to build a PipeLine that's exactly a BsonDocument like any other.
I had to query the database from C# using a similar approach to yours:
db.mycoll.aggregate(
[ { $geoNear :
{ near : { type : "Point", coordinates : [-34.5460069,-58.48894900000001] },
distanceField : "dist.calculated", maxDistance : 100,
includeLocs : "dist.location",
num : 5, spherical : true }
} ,
{ $project : {_id: 1, place_id:1, name:1, dist:1} }
] ).pretty()
As you know there's not a geoNear method to build it in the driver, so you can just create the BsonDocument.
To show you that everything can be built in that way find below a sample query from C# not using the project clausule either, I built it from the BsonDocument. You can push BsonDocument's in the pipeline as you wish.
var coll = _database.GetCollection<UrbanEntity>("mycoll");
var geoNearOptions = new BsonDocument {
{ "near", new BsonDocument {
{ "type", "Point" },
{ "coordinates", new BsonArray {-34.5460069,-58.48894900000001} },
} },
{ "distanceField", "dist.calculated" },
{ "maxDistance", 100 },
{ "includeLocs", "dist.location" },
{ "num", 5 },
{ "spherical" , true }
};
var projectOptions = new BsonDocument {
{ "_id" , 1 },
{ "place_id", 1 },
{ "name" , 1 },
{ "dist", 1}
};
var pipeline = new List<BsonDocument>();
pipeline.Add( new BsonDocument { {"$geoNear", geoNearOptions} });
pipeline.Add( new BsonDocument { {"$project", projectOptions} });
using(var cursor = await coll.AggregateAsync<BsonDocument>(pipeline)) {
while(await cursor.MoveNextAsync()) {
foreach (var doc in cursor.Current) {
// Here you have the documents ready to read
}
}
}
I hope it helps.