Sorting within CosmosDB based on different parameters - c#

I have a problem where it comes to custom sorting.
I need to sort elements based on 3 factors:
ID (string)
Flag (boolen)
Date (datetime
Example fiddle code: https://dotnetfiddle.net/OSicuK
Sample data records:
{
"id": "cbb440a8-fc26-4d30-966c-eba114410c6b",
"creationDate": "2021-05-25T00:00:00",
"child": false,
"parentId": "cbb440a8-fc26-4d30-966c-eba114410c6b"
},
{
"id": "cf70fbef-aa4f-45d3-96ab-3e211c290765",
"creationDate": "2021-05-27T00:00:00",
"child": true,
"parentId": "cbb440a8-fc26-4d30-966c-eba114410c6b"
},
{
"id": "48a82712-3e72-3ca7-a02d-edafd170f0b5",
"creationDate": "2021-06-17T00:00:00",
"child": false,
"parentId": "48a82712-3e72-3ca7-a02d-edafd170f0b5"
},
{ (this one should be ealier since creationDate is ealier than one above and both child is set to false)
"id": "1e4372f9-54dd-45b1-a401-56accbefc936",
"creationDate": "2021-05-25T00:00:00",
"child": false,
"parentId": "1e4372f9-54dd-45b1-a401-56accbefc936"
},
{
"id": "cf70fbef-aa4f-45d3-96ab-3e211c290765",
"creationDate": "2021-05-27T00:00:00",
"child": true,
"parentId": "1e4372f9-54dd-45b1-a401-56accbefc936"
},
{
"id": "82d7ea62-8f30-4bfd-be27-eb79c0f5e9e9",
"creationDate": "2021-05-27T00:00:00",
"child": true,
"parentId": "1e4372f9-54dd-45b1-a401-56accbefc936"
},
{
"id": "48a82712-3e72-3ca7-a02d-edafd170f0b5",
"creationDate": "2021-06-17T00:00:00",
"child": true,
"parentId": "1e4372f9-54dd-45b1-a401-56accbefc936"
}
Records need to be sorted from oldest parent to the newest, when child flag is set to false then record needs to be before all records with the same parentId but child flag set to true.
I've tried following:
return documents .OrderBy(x => x.parentId) .ThenBy(x => x.child) .ThenBy(x => x.creationDate);
Unfortunately CreationDate is not sorted correctly.
Thanks in advance!

What you're missing is the grouping of data.
Without grouping you can either sort on ParentId or on CreationDate, but you want to sort the parents with their children (ie grouped), in the order of the parents' CreationDate.
var results = list
.OrderBy(x => x.CreationDate)
.ThenBy(x => x.Child)
.GroupBy(x => x.ParentId)
.SelectMany(x => x);

The issue you are facing can be resolved by changing the sorting order. You can use OrderByDescending instead of OrderBy.
Below is the sample code:-
return documents.OrderByDescending(x => x.parentId).ThenBy(x =>
x.child).ThenBy(x => x.creationDate);

Related

LINQ query orderBy a subquery

How do I order by date using LINQ query for an included table
public async Task<IList<TaskSchedule>> GetTask(int id)
{
var taskSchedule = await _context.TaskSchedules
.Where(u => u.Id == id)
.Include(ts => ts.Notes.OrderBy(i => i.DateCreated)) //this is what needs to be in ascneding order of date
.Include(ts => ts.Attachments)
.Include(c => c.customer)
.ToListAsync();
return taskSchedule;
}
Below code works, however, it does not sort out the notes in date ascending order
public async Task<IList<TaskSchedule>> GetTask(int id)
{
var taskSchedule = await _context.TaskSchedules
.Where(u => u.Id == id)
.Include(ts => ts.Notes)
.Include(ts => ts.Attachments)
.Include(c => c.customer)
.ToListAsync();
return taskSchedule;
}
The error message i get is a 500 (Internal Server Error)
System.ArgumentException: Expression of type 'System.Linq.IOrderedQueryable1[Schedular.API.Models.Note]' cannot be used for return type 'System.Linq.IOrderedEnumerable1[Schedular.API.Models.Note]'
This is the API data that comes back when I use the working code. The notes need to be in the order of the date it was created. Currently, it's the other way around.
[
{
"id": 102,
"title": "this should display",
"start": null,
"end": null,
"isClosed": false,
"highPriority": false,
"hasTimeLimit": false,
"customer": null,
"customerId": null,
"notes": [
{
"id": 70,
"notesInfo": "add some notes first",
"dateCreated": "2020-11-17T12:20:00",
"user": null,
"userId": 1,
"taskScheduleId": 102
},
{
"id": 72,
"notesInfo": "add some notes second",
"dateCreated": "2020-11-18T16:35:00",
"user": null,
"userId": 1,
"taskScheduleId": 102
},
{
"id": 73,
"notesInfo": "add some notes third",
"dateCreated": "2020-11-19T18:35:00",
"user": null,
"userId": 1,
"taskScheduleId": 102
}
],
"attachments": [],
"userCurrentAssignedId": 1,
"userCurrentAssigned": null,
"userLastEditId": 1,
"userLastEdit": null,
"userLastEditDate": "2020-11-19T15:37:00",
"taskCreatedDate": "2020-11-19T15:37:00"
}
]
It is not valid use OrderBy inside Include,change like below:
var taskSchedule = await _context.TaskSchedules
.Where(u => u.Id == id)
.Include(ts => ts.Notes)
.Include(ts => ts.Attachments)
.Include(c => c.customer)
.ToListAsync();
taskSchedule.ForEach(t => t.Notes = t.Notes.OrderBy(n => n.DateCreated).ToList());
To sum-up this question and comments.
This query originally supported only by EF Core 5 and later. Include is not a subquery it's a directive for EF Core to load related entities and evolution of this functionality is introduced in EF Core 5.
Anyway, usually it is not needed to do such queries because they are related to DTO mapping. So just do such mapping by hands and this query should work for EF Core 3.x also.
var taskSchedule =
from ts in await _context.TaskSchedules
where ts.Id == id
select new TaskScheduleDTO
{
Notes = ts.Notes.OrderBy(n => n.DateCreated).ToList(),
Attachments = ts.Attachments.ToList(),
Cutomser = ts.Customer
}

Nest search query returning different results from the Elastic query DSL

I have an ElasticSearch index called challenges, which contains objects of type Challenge.
When I execute the following filter query in the Kibana console, it returns the 9 results, which are correct.
GET challenges/_search
{
"query": {
"bool": {
"filter": [
{
"term": {
"type": "Orphan"
}
}
]
}
}
}
However, the following query from the Nest client, returns zero hits:
var challenges = await _client.SearchAsync<Challenge>(s => s
.Query(q => +q
.Term(t => t.Type, Models.Enums.ChallengeType.Orphan)
)
);
I've also tried the following variation, to no avail:
var challenges = await _client.SearchAsync<Challenge>(s => s
.Query(q => q
.Bool(b => b
.Filter(f => f
.Term(t => t
.Field(f => f.Type)
.Value(challengeType)
)
)
)
)
);
The type property on which I'm filtering, is an enum with the following values:
public enum ChallengeType
{
SixDimensions,
Intro,
Normal,
UserCreated,
Orphan,
Youmate
}
and is stored as a keyword in the index.
An example object that is actually in the index:
{
"id": "3bce0ce1-9676-4858-b165-1442a443bf5a",
"icon": "water-bottle.png",
"index": 0,
"default-time": "09:00",
"default-days": [
"Saturday",
"Monday",
"Wednesday"
],
"default-repetitions": 3,
"category": "A",
"title": {
"Persian": "آب خوردن"
},
"dimension": "Physical",
"type": "Orphan",
"id-package": "00000000-0000-0000-0000-000000000000",
"intro-pages": [ ],
"date-created": "2020-10-14T12:39:21.8427517+03:30",
"notify": true,
"template": 0,
"belongs-to-user": "00000000-0000-0000-0000-000000000000",
"active": false
}
Do you have any suggestions as to why the results from the console differ from when it is executed from the Nest client?
The enum should be marked with StringEnumAttribute to serialize as a string
[StringEnum]
public enum ChallengeType
{
SixDimensions,
Intro,
Normal,
UserCreated,
Orphan,
Youmate
}
Without this, the enum will be serialized as its underlying integer value.

LINQ Get Average values from IQueryable

I'm new to LINQ and I need to write a LINQ query that returns each project's grade, called notet also the average of all notes.
Here is the query I have:
`var query = _context.Set<EvaluationResult>()
.Include(x => x.RatingLevel)
.Include(x => x.Skill)
.Include(x => x.Evaluation)
.ThenInclude(y => y.Project)
.ThenInclude(z => z.ProjectRatingLevels)
.ThenInclude(a => a.RatingLevel)
.Include(y => y.Evaluation.Project)
.ThenInclude(y => y.Degree)
.Where(x => x.Evaluation.Project.DegreeId == QueriedDegreeId)
.GroupBy(i => new { project = i.Evaluation.Project })
.Select(g => new
{
project = g.Select(y => y.Evaluation.Project.Label)
.Distinct().FirstOrDefault(),
note = Math.Round(((g.Sum(y => (double)y.Skill.Weight * (double)y.RatingLevel.Rate) /
g.Sum(y => (double)y.RatingLevel.Rate)) * 100) /
(double)g.Key.project.ProjectRatingLevels
.Select(z => z.RatingLevel.Rate)
.Max(), 2, MidpointRounding.AwayFromZero)
});
Here is the result:
[
{
"project": "Projet 1",
"note": 42.86
},
{
"project": "Projet 2",
"note": 41.67
},
{
"project": "Projet 3",
"note": 46.67
}
]
What I want is to add another value, average, which is just the Average of all "note" values, like so (the asterisks are just for emphasis):
[
{
"project": "Projet 1",
"note": 42.86,
**"average": 43.73**
},
{
"project": "Projet 2",
"note": 41.67,
**"average": 43.73**
},
{
"project": "Projet 3",
"note": 46.67,
**"average": 43.73**
}
]
MY PROBLEM
I'm stuck trying to calculate an Average of all the returned notes. I have no idea how to proceed. I tried to add an average key in my Select after note and project that reused the note query, like this:
average = g.Average(Math.Round(((g.Sum(... etc
But this gives me type errors: CS1929 'IGrouping<<anonymous type: Project project>, EvaluationResult>' does not contain a definition for average 'Average'. So I'm at an utter loss of what to do.
Can someone point me in the right direction? Am I missing something, like the need to use a Key or something?
Is your goal to do it all in one query ?
The only possible Resultset, with Aggregate and the Aggregated Items, is a GroupBy.
If you want to aggregate all, you want only one group, so you have to have a fictive key,the same one for each item
So Append:
.GroupBy(x => 1) /* makes one group for all, all have the same key */
.Select(g => new { average = g.Average(x => x.notes), items = g.Select(x => x)});
But this is really Forcing the SQL-Server to do the average. You Manifest the items in your memory anyway. So you can also take your existing Resultset, manifested with ToList or ToArray and just compute
var result = <yourquery>.ToList();
double average = result.Average(x => x.notes);
The only difference is, this is done on your CPU, not on the SQL-Server.

Querying a subfield in DocumentDB to sort and get the latest date

To add/expound on my recent question
Below are DocumentDB collections: "delivery"
{
"doc": [
{
"docid": "15",
"deliverynum": "123",
"text": "txxxxxx",
"date": "2019-07-18T12:37:58Z"
},
{
"docid": "17",
"deliverynum": "999",
"text": "txxxxxx",
"date": "2018-07-18T12:37:58Z"
}
],
"id": "123",
"cancelled": false
},
{
"doc": [
{
"docid": "16",
"deliverynum": "222",
"text": "txxxxxx",
"date": "2019-07-18T12:37:58Z"
},
{
"docid": "17",
"deliverynum": "999",
"text": "txxxxxx",
"date": "2019-07-20T12:37:58Z"
}
],
"id": "124",
"cancelled": false
}
I need to search the deliverynum=999 w/ the latest date to get the "id", which in the case above is "124" because it has the latest "date" in the "doc" w/ deliverynum=999.
I was going to do:
var list = await collection.Find(filter).Project(projection).ToListAsync();
then do a LINQ to sort, but the problem here is my projection change the list from my Model Class to BsonDocument even if my projection included all the fields.
Was looking for a way to either get just needed "id" or get the single document.
i believe the following will do the trick. (if i understood your requirement correctly)
var result = collection.Find(x => x.docs.Any(d => d.deliverynum == 999))
.Sort(Builders<Record>.Sort.Descending("docs.date"))
.Limit(1)
.Project(x=>x.Id) //remove this to get back the whole record
.ToList();
update: strongly typed solution
var result = collection.AsQueryable()
.Where(r => r.docs.Any(d => d.deliverynum == 999))
.SelectMany(r => r.docs, (r, d) => new { r.Id, d.date })
.OrderByDescending(x => x.date)
.Take(1)
.Select(x => x.Id)
.ToArray();

GroupBy - is this expected behavior?

I have simple EAV'ish scenario where User can be in multiple Usergroup and Usergroup can have multiple Field. I select user, select all his usergroups and then show the fields.
Problem is that I want not to show fields with duplicate Key property.
Current situation
Fields = user.Usergroups
.SelectMany(x => x.UsergroupFields)
.Select(field => new
{
field.Key
})
Product
"Fields": [
{
"Key": "field 1"
},
{
"Key": "field 1"
},
{
"Key": "field 2"
}
]
As you can see I have multiple field 1, I want to remove duplicates based on Key property. I tried to do GroupBy() but it is doing something weird.
GroupBy()
Fields = user.Usergroups
.SelectMany(x => x.UsergroupFields)
.GroupBy(field => field.Key)
.FirstOrDefault()
.Select(field => new
{
field.Key
})
Results in
"Fields": [
{
"Key": "field 1"
},
{
"Key": "field 1"
}
]
It seems that GroupBy() is doing complete opposite of what I want to achieve.
Fields = user.Usergroups
.SelectMany(x => x.UsergroupFields)
.GroupBy(field => field.Key)
.Select(g=>g.First());
GroupBy also has an overload taking 2 arguments which can be applied in this case:
Fields = user.Usergroups
.SelectMany(x => x.UsergroupFields)
.GroupBy(field=>field.Key, (key, g)=>g.First());

Categories