C# Mongodb select rows by their sequence number in one query - c#

I have an object in the db which contains an array of strings.
{
"_id": ObjectId("abc123..."),
"Lines":[
"string 1",
"string 2",
"string 3",
"...",
"string 100"
]
}
I need to select rows by their sequence number in one query.
For example, I need lines 1, 7 and 15.
var projection = Builders<ContentEntity>.Projection
.Slice(e => e.Lines2, 1, 1)
.Slice(e => e.Lines2, 7, 1)
.Slice(e => e.Lines2, 15, 1);
var entity = await GetContext(categoryId).ContentEntities
.Find(e => e.Id == contentId)
.Project<ContentEntity>(projection)
.FirstOrDefaultAsync(cancellationToken);
return entity;
If I use the slice operator like this, then I only get row #15.
I need to select rows by their sequence number in one query from Mongodb with ะก#.

Think that the last .Slice() overwrite the previous .Slice().
Pass the query as BsonDocument instead of using the MongoDB Driver Fluent API which is difficult to implement for your scenario.
Query
db.collection.find({},
{
Lines2: {
$filter: {
input: "$Lines",
cond: {
$in: [
{
$indexOfArray: [
"$Lines",
"$$this"
]
},
[
0,
6,
14
]
]
}
}
}
})
Demo # Mongo Playground
MongoDB .NET Driver syntax
var projection = new BsonDocument
{
{
"Lines", new BsonDocument
{
{
"$filter", new BsonDocument
{
{ "input", "$Lines" },
{
"cond", new BsonDocument
{
{
"$in", new BsonArray
{
new BsonDocument("$indexOfArray", new BsonArray
{
"$Lines",
"$$this"
}),
new BsonArray { 0, 6, 14 }
}
}
}
}
}
}
}
}
};

Related

MongoDB.NET Driver query for $lte and $gte throwing error: An object representing an expression must have exactly one field

FilterDefinition<DM.Content> filterDefinition = Builders<DM.Content>.Filter.Empty;
filterDefinition &= Builders<DM.Content>.Filter.Eq(x => x.IsDeleted, false);
if (contentTypeId > 0)
{
if (contentTypeId == 4)// Photo Video recipes
{
filterDefinition &= Builders<DM.Content>.Filter.In(x => x.ContentTypeId, new List<int>() { 1, 2 });// Video Photo recipes Recipes
}
else
{
filterDefinition &= Builders<DM.Content>.Filter.Eq(x => x.ContentTypeId, contentTypeId);
}
}
// rating
if (!string.IsNullOrEmpty(rating))
{
//filterDefinition &= Builders<DM.Content>.Filter.Gte(x => x.ContentAverageRating, rating);
filterDefinition &= new BsonDocument("$expr", new BsonDocument("$gte",
new BsonArray {
new BsonDocument("$toDouble", "$ContentAverageRating"),
Convert.ToDouble(rating)
}));
}
// cookTime
if (!string.IsNullOrEmpty(cookTime))
{
filterDefinition &= new BsonDocument("$expr", new BsonDocument("$lte",
new BsonArray {
new BsonDocument("$toDouble", "$ContentTime"),
Convert.ToDouble(cookTime)
}));
}
SortDefinition<DM.Content> sortDefinition = Builders<DM.Content>.Sort.Descending(x => x.UpdatedDate);
var results = await contentDocument.AggregateByPage(filterDefinition, sortDefinition, pageIndex, pageSize);
return new DM.CustomModels.ContentSearchResult() { ContentItems = results.data.ToList(), TotalPages = results.totalPages };
public static async Task<(int totalPages, IReadOnlyList<TDocument> data)> AggregateByPage<TDocument>(
this IMongoCollection<TDocument> collection,
FilterDefinition<TDocument> filterDefinition,
SortDefinition<TDocument> sortDefinition,
int page,
int pageSize)
{
var countFacet = AggregateFacet.Create("count",
PipelineDefinition<TDocument, AggregateCountResult>.Create(new[]
{
PipelineStageDefinitionBuilder.Count<TDocument>()
}));
var dataFacet = AggregateFacet.Create("data",
PipelineDefinition<TDocument, TDocument>.Create(new[]
{
PipelineStageDefinitionBuilder.Sort(sortDefinition),
PipelineStageDefinitionBuilder.Skip<TDocument>((page - 1) * pageSize),
PipelineStageDefinitionBuilder.Limit<TDocument>(pageSize),
}));
var aggregation = await collection.Aggregate()
.Match(filterDefinition)
.Facet(countFacet, dataFacet)
.ToListAsync();
var count = aggregation.First()
.Facets.First(x => x.Name == "count")
.Output<AggregateCountResult>()?
.FirstOrDefault()?
.Count ?? 0;
var totalPages = (int)Math.Ceiling((double)count / pageSize);
var data = aggregation.First()
.Facets.First(x => x.Name == "data")
.Output<TDocument>();
return (totalPages, data);
}
I am trying to execute the above query to check ContentAverageRating, ContentTime with certain conditions. But throwing an exception:
Command aggregate failed: An object representing an expression must have exactly one field: { $gte: [ { $toDouble: "$ContentAverageRating" }, 3.0 ], $lte: [ { $toDouble: "$ContentTime" }, 15.0 ] }.
Can anyone let me know what is wrong with the above query?
Issue & Concern
From what I suspect, MongoDB .Net driver had placed both $gte and $lte under the same $expr value, which make the whole BsonDocument failed.
Expected generated Bson Document
{
$and: [
{ $expr: { $gte: [/* Match Condition */] } },
{ $expr: { $lte: [/* Match Condition */] } }
]
}
Actual generated Bson Doccument
{
$and: [
{ $expr: { $gte: [/* Match Condition */], $lte: [/* Match Condition */] } }
]
}
Solution
After some trial and error, I suggest that remove the BsonDocument with $expr from both $lte and $gte. Then you create another FilterDefinition, rootFilterDefinitionto place $expr at top level, then append the filterDefinition as below:
filterDefinition &=
new BsonDocument("$gte",
new BsonArray {
new BsonDocument("$toDouble", "$ContentAverageRating"),
Convert.ToDouble(rating)
}
);
filterDefinition &=
new BsonDocument("$lte",
new BsonArray {
new BsonDocument("$toDecimal", "$ContentTime"),
Decimal.Parse(cookTime)
}
);
FilterDefinition<BsonDocument> rootFilterDefinition = new BsonDocument("$expr",
filterDefinition.ToBsonDocument());
var results = await contentDocument.AggregateByPage(rootFilterDefinition , sortDefinition, pageIndex, pageSize);
Equivalent to this MongoDB query
db.collection.aggregate([
{
$match: {
$expr: {
$and: [
{
$gte: [
{
$toDouble: "$ContentAverageRating"
},
3.0
]
},
{
$lte: [
{
$toDouble: "$ContentTime"
},
15.0
]
}
]
}
}
}
])
Sample Mongo Playground

$[<identifier>] operator syntax in C# MongoDB Driver for nested array update in a Document

I need to update point Description field highlighted inside the nested Document using C# Mongo DB driver. I can do this update successfully using following query.
Document Structure
{
"ControllerID": "testcon",
"CCNID": "testccn",
"TableGroup": 1,
"Tables": [
{
"ID": 0,
"TableGroupID": 1,
"Blocks": [
{
"ID": 0,
"TableID": 0,
"TableGroupID": 1,
"ControllerID": "testcon",
"Points": [
{
"BlockID": 0,
"TableGroupID": 1,
"ControllerID": "testcon",
"TableID": 0,
"PointDefinitionID": 23,
"Name": "Test Point",
"Description": "Hgfhdhfhey You" <----------- This field needs to be updated
},
{
"BlockID": 0,
"TableGroupID": 1,
"ControllerID": "testcon",
"TableID": 0,
"PointDefinitionID": 24,
"Name": "Test Point",
"Description": "Hgfhdhfhey You"
}
]
}
]
}
]
}
I can successfully update the point Description using this query.
db.ControllerPointCollection.updateOne({
"_id": "HRDC_testccn_0_34_1"
},
{
$set: {
"Tables.$[t].Blocks.$[b].Points.$[p].Description": "Hey You"
}
},
{
arrayFilters: [
{
"t.ID": 0
},
{
"b.ID": 0
},
{
"p.PointDefinitionID": 23
}
]
})
I tried using this filter and update object for the above operation
var pointFilter = Builders<Point>.Filter.Eq(p => p.PointDefinitionID, point.PointDefinitionID);
var blockFilter = Builders<Block>.Filter.Eq(b => b.ID, point.BlockID) & Builders<Block>.Filter.ElemMatch(b => b.Points, pointFilter);
var tableFilter = Builders<Table>.Filter.Eq(t => t.ID, point.TableID) & Builders<Table>.Filter.ElemMatch(t => t.Blocks, blockFilter);
var filter = Builders<ControllerPointDataDoc>.Filter.Eq(c => c.ID, point.ControllerID) & Builders<ControllerPointDataDoc>.Filter.ElemMatch(c => c.Tables, tableFilter);
var updater = Builders<ControllerPointDataDoc>.Update.Set(c => c.Tables[-1].Blocks[-1].Points[-1].Description, "Hey You");
operationResult.Data = await ControllerPointDataCollection.UpdateOneAsync(filter, updater);
but I am getting the following error.
A write operation resulted in an error.\r\n Too many positional (i.e. '$') elements found in path 'Tables.$.Blocks.$.Points'

MongoDB in C# - what is the right way to perform multiple filtering

I have a MongoDB collection of Persons (person_Id, person_name, person_age)
I want to return an object that contains:
number_of_all_persons
number_of_all_persons with age < 20
number_of_all_persons with age >= 20 && age <= 40
number_of_all_persons with age > 40
What is the right way to do it in Mongo using C#?
Should I run 4 different Filters to achieve result?
You can use Aggregation.
const lowerRange = { $lt: ["$person_age", 20] };
const middleRange = { $and: [{ $gte: ["$person_age", 20]}, { $lte: ["$person_age", 40] }] };
// const upperRange = { $gt: ["$person_age", 40] };
db.range.aggregate([
{
$project: {
ageRange: {
$cond: {
if: lowerRange,
then: "lowerRange",
else: {
$cond: {
if: middleRange,
then: "middleRange",
else: "upperRange"
}
}
}
}
}
},
{
$group: {
_id: "$ageRange",
count: { $sum: 1 }
}
}
]);
Here the total count is not included as you can calculate it from the count of the ranges. If you have more that 3 ranges, a better idea would be to pass an array of $cond statements to the project stage, as nesting multiple if/else statements starts to get harder to maintain. Here is a sample:
$project: {
"ageRange": {
$concat: [
{ $cond: [ { $lt: ["$person_age", 20] }, "0-19", ""] },
{ $cond: [ { $and: [ { $gte: ["$person_age", 20] }, { $lte: ["$person_age", 40] } ] }, "20-40", ""] },
{ $cond: [ { $gt: ["$person_age", 40] }, "41+", ""] }
]
}
}
You have several possible solutions here.
Filter with mongo
Filter with Linq
Looks like you are having an "and" filter here. You can do your filtering as described here: Link to filtering
Or, depending on how large your persons collection is, you could do query for all objects and filter with linq:
var collection.FindAll().Where(x => (x.age < 20) && (x.age >= 20 && x.age <= 40) ...)
The above is not syntax correct but I hope you get the idea behind it.
My approach would be creating a filter builder and and adding it to the find method call.
var highExamScoreFilter = Builders<BsonDocument>.Filter.ElemMatch<BsonValue>(
"scores", new BsonDocument { { "type", "exam" },
{ "score", new BsonDocument { { "$gte", 95 } } }
});
var highExamScores = collection.Find(highExamScoreFilter).ToList();
Hope this helps somehow.

Linq query to filter multi level classes

I have my departments data coming from the database. I want to filter this data based on certain criteria.
[
{
"Id":10,
"Name":"Name 10",
"Teachers":[
{
"TeacherId":100,
"TeacherName":null,
"DepartmentId":100,
"Students":[
{
"StudentId":1001,
"StudentName":null,
"TeacherId":10,
"DepartmentId":100
}
]
},
{
"TeacherId":101,
"TeacherName":null,
"DepartmentId":100,
"Students":[
{
"StudentId":1001,
"StudentName":null,
"TeacherId":10,
"DepartmentId":100
}
]
}
]
},
{
"Id":100,
"Name":"Name 10",
"Teachers":[
{
"TeacherId":0,
"TeacherName":null,
"DepartmentId":100,
"Students":[
{
"StudentId":5000,
"StudentName":null,
"TeacherId":50,
"DepartmentId":100
}
]
}
]
},
{
"Id":50,
"Name":"Name 10",
"Teachers":[
{
"TeacherId":0,
"TeacherName":null,
"DepartmentId":100,
"Students":[
{
"StudentId":2000,
"StudentName":null,
"TeacherId":50,
"DepartmentId":100
}
]
}
]
}
]
Now I have to filter the departments based on some values as shown below
var departmenIds = new List<int>() { 10, 20, 30 };
var teachers = new List<int>() { 100, 200, 300 };
var students = new List<int>() { 1000, 2000, 3000 };
I am looking for a query that will return the data in a following fashion
If all department ids exists in the json it will return entire data. If a department with a particular teacher is in the list then only return that teacher and the department. like wise for the student.
I tried this to test if it atleast work at the second level but I am getting all the teachers
var list = allDeplrtments.Where(d => d.Teachers.Any(t => teachers.Contains(t.TeacherId))).ToList();
var list = allDepartments
.Where(d => departmentIds.Contains(d.Id))
.Select(d => new Department() {
Id = d.Id,
Name = d.Name,
Teachers = (d.Teachers.Any(t => teacherIds.Contains(t.TeacherId))
? d.Teachers.Where(t => teacherIds.Contains(t.TeacherId))
: d.Teachers)
.Select(t => new Teacher() {
TeacherId = t.TeacherId,
TeacherName = t.TeacherName,
DepartmentId = d.Id,
Students = t.Students.Any(s => studentIds.Contains(s.StudentId))
? t.Students.Where(s => studentIds.Contains(s.StudentId))
: t.Students
})
})
Would something like this work for you?

Can't do multiple groups in MongoDB C# Driver

I am trying to show a statistics count, per month, per manufacturer from a MongoDB collection using Linq.I would like to see my data as:
[{
Manufacturer: '',
Statistics: [{
Month: '',
Counts: 0
}]
}]
So I am trying the following linq query:
var result = _statisticsRepository
.All()
.GroupBy(g => g.ManufacturerId)
.Select(s => new
{
Manufacturer = s.Key,
Statistics = s
.GroupBy(a => a.Values["DATA_MONTH"])
.Select(a => new
{
Month = a.Key,
Counts = a.Sum(d => d.Count)
})
});
When I try this code I get this error:
NotSupportedException: The method GroupBy is not supported in the expression tree: {document}.GroupBy(a => a.Values.get_Item("DATA_MONTH")).
Am I doing this the wrong way or is this a limitation I have in the C# driver? Or is there another way to do this?
You could use the aggregation framework instead LINQ to get the data that you need:
var group1 = new BsonDocument
{
{ "_id", new BsonDocument{{"ManufacturerId", "$ManufacturerId"},{"DATA_MONTH", "$DATA_MONTH"} }},
{ "Count", new BsonDocument
{
{
"$sum", $Count }
} }
};
var group2 = new BsonDocument
{
{ "_id", "$_id.ManufacturerId" },
{ "Statistics ", new BsonDocument { { "$addToSet",new BsonDocument{{"Month", "$_id.DATA_MONTH"},{"Count","$Count"} } } }},
};
var result = database.GetCollection<BsonDocument>("Manufacturers").Aggregate()
.Group(group1)
.Group(group2)
.ToList();

Categories