I have a collection of 10 000 documents, (~ 6 Mb)
When I'm using a simple find to retrieve all the data, it takes like more than a minute to retrieve all the documents.
I created a search Index on MongoDb to speed up what I want to achieve.
I have also a search Index :
Here is the Query Syntax of this Search Index :
new BsonArray {
new BsonDocument("$search", new BsonDocument {
{
"index",
"searchProd"
},
{
"text",
new BsonDocument {
{
"query",
application // DYNAMIC Value that I want to pass
},
{
"path",
new BsonDocument("wildcard", "*")
}
}
}
})
}
Couldn't find a way how to implement this on my function.
public async Task<List<Produit>> ListAllProducts(string application)
{
var sw = Stopwatch.StartNew();
var allProducts = await _produits.Find(new BsonDocument("$search", new BsonDocument
{
{
"index",
"searchProd"
},
{
"text",
new BsonDocument
{
{
"query",
application
},
{
"path",
new BsonDocument("wildcard", "*")
}
}
}
})).ToList();
return allProducts;
}
By doing this I'm getting
MongoDB.Driver.MongoCommandException: Command find failed: unknown top
level operator: $search. If you have a field name that starts with a
'$' symbol, consider using $getField or $setField..
Thanks.
atlas $search is an aggregation stage, not find argument. It should look similar to:
_produits.Aggregate().AppendStage<BsonDocument>("your $search stage in string or BsonDocument form").ToList();
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 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();
The data:
The collection contains a list of audit records and I want to return the last modified items from the collection.
For example:
So the query needs to return Audit 1235 and 1237 Only.
The following statement works in Mongo Shell and returns the data sub-millisecond, I just need to also figure out how to return the entire Collection item instead of just the Id.
db.Forms.aggregate(
{ $group: { _id: "$Id", lastModifiedId: { $last: "$_id" } } }
)
However, I need to convert this to the C# Driver's syntax.
I have the following at the moment but it's not working and returns (for lack of a better term) weird data (see screencap under the statement).
var results = collection.Aggregate()
.Group(new BsonDocument { { "_id", "$Id" }, { "lastModifiedId", new BsonDocument("$last", "_id") } })
.ToListAsync().Result.ToList();
My current solution gets the full collection back and then runs it through an extension method to get the latest records (where list is the full collection):
var lastModifiedOnlyList =
from listItem in list.OrderByDescending(_ => _.AuditId)
group listItem by listItem.Id into grp
select grp.OrderByDescending(listItem => listItem.AuditId)
.FirstOrDefault();
While this code works, it is EXTREMELY slow because of the sheer amount of data that is being returned from the collection, so I need to do the grouping on the list as part of the collection get/find.
Please let me know if I can provide any additional information.
Update: With Axel's help I managed to get it resolved:
var pipeline = new[] { new BsonDocument { { "$group", new BsonDocument { { "_id", "$Id" }, { "LastAuditId", new BsonDocument { { "$last", "$_id" } } } } } } };
var lastAuditIds = collection.Aggregate<Audit>(pipeline).ToListAsync().Result.ToList().Select(_=>_.LastAuditId);
I moved that to it's own method and then use the IDs to get the collection items back, with my projection working as well:
var forLastAuditIds = ForLastAuditIds(collection);
var limitedList = (
projection != null
? collection.Find(forLastAuditIds & filter, new FindOptions()).Project(projection)
: collection.Find(forLastAuditIds & filter, new FindOptions())
).ToListAsync().Result.ToList();
"filter" in this case is either an Expression or a BsonDocument. The performance is great as well - sub-second for the whole thing. Thanks for the help, Axel!
I think you're doing an extra OrderBy, this should do:
var lastModifiedOnlyList =
from listItem in list
group listItem by listItem.Id into grp
select grp.OrderByDescending(listItem => listItem.AuditId)
.FirstOrDefault();
EDIT:
To gain performance in the query, you could use the Aggregate function differently:
var match = new BsonDocument
{
{
"$group",
new BsonDocument
{
{ "_id", "$Id" },
{ "lastModifiedId", new BsonDocument
{
{
"$last", "$_id"
}
}}
}
}
};
var pipeline = new[] { match };
var result = collection.Aggregate(pipeline);
That should be the equivalent of your Mongo Shell query.
I have a collection of BsonDocuments that each have a field named "engagement" (which is a number) and also a field named "color". I would like total sum of cost for all BsonDocuments where "color" = "blue".
So far from what I've found is I need to do something like this:
var collection = db.GetCollection<BsonDocument>("collectionName");
var match = new BsonDocument
{
{
"$match",
new BsonDocument
{
{"sentiment", "positive"}
}
}
};
var group = new BsonDocument
{
{ "$group",
new BsonDocument
{
{
"Sum", new BsonDocument
{
{
"$sum", "$engagement"
}
}
}
}
}
};
var pipeline = new[] { match, group };
var result = collection.Aggregate(pipeline);
I've been getting an error about the type methods arguments for the Aggregate method cannot be inferred from the usage.
Simply put, I would like to know how to perform simple math operations with the the Aggregate Framework. In this example, I am trying to sum all the values of the engagement field from all of the BsonDocuments in my collection. The utilization of Builder functions would be great if possible but the manually created BsonDocument will serve just as well.
Thanks for your time
When you use $group, you need to tell the aggregation framework by which fields you want to group. You do that in the _id field to the group-object. Also, if you want a simple document count, you need to use { $sum : 1 } to count one per document. If you use a field-name, you tell the aggregation to interpret the values of that field as numbers and add them up.
In MongoDB shell syntax:
{ $group: {
_id: "$engagement",
Sum: { $sum: 1 }
}}
Translated to C# (untested!):
{ "$group", new BsonDocument
{
{ "_id", "$engagement" },
{ "Sum", new BsonDocument
{
{
"$sum", 1
}
}
}
}
}
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.