Create subquery for orderby - entity-framework

I have two related tables collections and items.
I want to order selected items based on collection properties.
Collection
class Collection
{
int Id;
string Title;
bool Override;
int Level;
Datetime Till;
int OtherProp;
virtual ICollection<Item> Items{ get; set; }
}
Item
class item
{
int Id;
int OrderId;
string Title;
virtual Collection Collection{ get; set; }
}
I want to select all items and order them in following order:
All items where order override is true;
All items where order level equals 1 and till is les than Datetime.Now();
All items where order level equals 2 and till is les than Datetime.Now();
All other items.
It would look like
var resultList = await _context.Items.Where(item=> item.OtherProp == 1)
.OrderBy(item => ??????????????)
.ThenBy(item=> item.Title).ToListAsync();

Try the following query:
var resultList = await _context.Items
.Where(item=> item.OtherProp == 1)
.OrderByDescending(item => item.Collection.Override)
.ThenByDescending(item => item.Collection.Till <= Datetime.Now() && item.Collection.Level == 1)
.ThenByDescending(item => item.Collection.Till <= Datetime.Now() && item.Collection.Level == 2)
.ThenBy(item => item.Title)
.ToListAsync();

var resultList = await _context.Items
.Where(item => item.OtherProp == 1)
.OrderBy(item =>
item.Collection.Override ? 0 :
(item.Collection.Level == 1 && item.Collection.Till < DateTime.Now) ? 1 :
(item.Collection.Level == 2 && item.Collection.Till < DateTime.Now) ? 2 : 3)
.ThenBy(item => item.Title)
.ToListAsync();
You can try this query with ternary operators
It does these steps:
If the item's collection has the Override property set to true, ordering value = 0.
If the item's collection has Level equal to 1 and Till is less than
DateTime.Now, ordering value = 1.
If the item's collection has Level equal to 2 and Till is less than DateTime.Now, ordering value = 2.
Otherwise, the item ordering value = 3.

Related

Generic Entity Framework Pagination

I am trying to write a generic way to provide pagination on my Link query to my MySQL database. However this required me to have a generic way to where, as the field used may differ from table to table. The issue i am facing it that Linq can't translate my generic types. Are there a way to do this that would work with Linq?
public static class PaginationExtentions {
public static async Task<PaginationResult<T>> Pagination<T, J>(this DbSet<J> dbSet, Pagination pagination, Func<IQueryable<J>, IQueryable<T>>? select) where J : class {
bool previousPage = false;
bool nextPage = false;
string startCursor = null;
string endCursor = null;
var property = typeof(J).GetProperty(pagination.SortBy);
string after = pagination.After != null ? Base64Decode(pagination.After) : null;
bool backwardMode = after != null && after.ToLower().StartsWith("prev__");
string cursor = after != null ? after.Split("__", 2).Last() : null;
List<J> items;
if (cursor == null) {
items = await (from s in dbSet
orderby GetPropertyValue<J, string>(s, pagination.SortBy) ascending
select s).Take(pagination.First)
.ToListAsync();
}
else if (backwardMode) {
items = await (from subject in (
from s in dbSet
where string.Compare( (string?)property.GetValue(s), cursor) < 0
orderby (string?)property.GetValue(s) descending
select s).Take(pagination.First)
orderby (string?)property.GetValue(subject) ascending
select subject).ToListAsync();
}
else {
items = await (from s in dbSet
where string.Compare((string?)property.GetValue(s), cursor) > 0
orderby GetPropertyValue<J, string>(s, pagination.SortBy) ascending
select s).Take(pagination.First)
.ToListAsync();
}
previousPage = await dbSet.Where(s => string.Compare((string?)property.GetValue(s), cursor) < 0).AnyAsync();
nextPage = items.Count == 0 ? false : await dbSet.Where(s => string.Compare((string?)property.GetValue(s), (string?)property.GetValue(items.Last())) > 0).AnyAsync();
var backwardsCursor = !previousPage ? null : "prev__" + cursor;
var forwardsCursor = !nextPage ? null : items.Count > 0 ? "next__" + (string?)property.GetValue(items.Last()) : null;
return new PaginationResult<T>() {
Items = select(items.AsQueryable()).ToList(),
TotalCount = await dbSet.CountAsync(),
HasPreviousPage = previousPage,
HasNextPage = nextPage,
StartCusor = Base64Encode(backwardsCursor),
EndCursor = Base64Encode(forwardsCursor)
};
}
private static U GetPropertyValue<T, U>(T obj, string propertyName) {
return (U)obj.GetType().GetProperty(propertyName).GetValue(obj, null);
}
public static string Base64Encode(string plainText) {
var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText);
return System.Convert.ToBase64String(plainTextBytes);
}
public static string Base64Decode(string base64EncodedData) {
var base64EncodedBytes = System.Convert.FromBase64String(base64EncodedData);
return System.Text.Encoding.UTF8.GetString(base64EncodedBytes);
}
}

Filters not working in my repository in ASP.NET Core

I have these parameters in a class:
public class UserParams
{
public string Gender {get; set;}
public int MinAge {get; set;} = 1;
public int MaxAge {get; set;} = 19;
}
The query is done in the repository as shown below. First is to query for the child sex or gender and the second is to query for the child sex or gender
var query = _context.Children.AsQueryable();
query = query.Where(c => c.Sex == userParams.Gender);
var minchildDob = DateTime.Today.AddYears(-userParams.MaxAge - 1);
var maxchildDob = DateTime.Today.AddYears(-userParams.MinAge);
query = query.Where(u => u.DateOfBirth >= minchildDob && u.DateOfBirth <= maxchildDob);
return await PagedList<Child>.CreateAsync(query.AsNoTracking(), userParams.PageNumber, userParams.PageSize);
The gender filter returns empty array of children and the minchildDob and maxchildDob too not working
if (!string.IsNullOrEmpty(temp.Gender))
{
all = all.Where(u => new[] { "men", "women" }.Contains(u.sex));
//all = all.Where(t => t.sex == temp.Gender);
}
=======================Update=======================
var temp = new UserParams();
temp.Gender = "men";
var minchildDob = DateTime.Today.AddYears(-temp.MaxAge - 1);
var maxchildDob = DateTime.Today.AddYears(-temp.MinAge);
IEnumerable<Table> all = from m in _context.data
select m;
_logger.LogError("all data");
foreach (var item in all)
{
_logger.LogError(item.name);
}
_logger.LogError("============================================");
if (!string.IsNullOrEmpty(temp.Gender)) {
all = all.Where(t => t.sex == temp.Gender);
}
_logger.LogError("filter gender");
foreach (var item in all) {
_logger.LogError(item.name);
}
_logger.LogError("============================================");
if (temp.MaxAge > 0) {
all = all.Where(t => t.birthday >= minchildDob && t.birthday <= maxchildDob);
}
_logger.LogError("filter age");
foreach (var item in all)
{
_logger.LogError(item.name);
}
_logger.LogError("============================================");

How to convert List<Object> flutter

i'm new in flutter and need to help:
I have already got
final List<Genres> genres = [{1,"comedy"}, {2,"drama"},{3,"horror"}]
from api.
class Genres {
final int id;
final String value;
Genres({this.id,this.value});
}
In another method I get genres.id.(2) How can I convert it to genres.value ("drama")?
Getting a Genre from an id is inconvenient when your data structure is a List. You have no choice but to iterate over the list and compare the id value to the id of each element in the list:
final id = 2;
final genre = genres.firstWhere((g) => g.id == id, orElse: () => null);
The problem with this code is that it's slow and there could be multiple matches (where the duplicates after the first found would be ignored).
A better approach would be to convert your list to a Map when you first create it. Afterwards, you can simply use an indexer to get a Genre for an ID quickly and safely.
final genresMap = Map.fromIterable(genres, (item) => item.id, (item) => item);
// later...
final id = 2;
final genre = genresMap[id];
This way, there is guaranteed to not be any duplicates, and if an ID doesn't exist then the indexer will simply return null.
you could iterate over the json result of the api and map them to the Gener class like so,
void fn(id) {
final gener = geners.firstWhere((gener) => gener['id'] == id);
// now you have access to your gener
}
You can find the item inside the List<Genres> like this
Genres element = list.firstWhere((element) => element.id == 2); // 2 being the id you give in the question as an exaple. You should make it dynamic
print(element.value);

Get total count of days between two dates Linq C#

I want to get total number of days between two dates using Linq C# with Entity Framework 6. My issue is I need a where clause condition and finding it difficult to subtract the dates. I am getting errors when I run my code below. Any help to achieve this will be appreciated.
var finalexams = report.Select(x =>
new ReportVieModel
{
EnglishDates = x.admin.mastertablebles.Where(d => d.EndDate == null).Select(s => DbFunctions.DiffDays(s.beginDate, DateTime.Today)),
}).Distinct();
What type of data does EnglishDate represent?
The only condition I see is that the final date can be null, I suppose this will return more than one value and you will need a list.
I don't understand if you want a list with the start date and the number of days because you could do this according to the model:
Model:
public class ReportViewModel
{
public DateTime? Date { get; set; }
public int? NumberDays { get; set; }
}
LINQ:
List<ReportViewModel> list = (from p in x.admin.mastertablebles
where p.EndDate == null
select new ReportViewModel() { Date = p.beginDate, NumberDays = DbFunctions.DiffDays(p.beginDate, DateTime.Now) })
.Distinct().ToList();
Or you want a list that only contains the number of days:
LINQ:
var numberDays = (from p in x.admin.mastertablebles
where p.EndDate == null
select DbFunctions.DiffDays(p.beginDate, DateTime.Now))
.Distinct().ToList();

conditional where in linq query

I want to return the total sum from a linq query, I pass in 2 parameters that may/may not be included in the query.
OrgId - int
reportType - int
So two questions:
How can I update the query below so that if OrgId = 0 then ignore the organisation field(Return All)?
LocumClaimTF can be True/False/Both, if both then ignore the where query for this field.
Heres what I have done so far, this is working but I'd like something for efficient.
// Using reportType set preferences for LocumClaimTF
bool locumClaimTF1, locumClaimTF2 = false;
if (reportType == 0)
{
locumClaimTF1 = false;
locumClaimTF2 = false;
}
else if (reportType == 1)
{
locumClaimTF1 = true;
locumClaimTF2 = true;
}
else // 2
{
locumClaimTF1 = true;
locumClaimTF2 = false;
}
if (OrgID != 0) // Get by OrgID
{
return _UoW.ShiftDates.Get(x => x.shiftStartDate >= StartDate && x.shiftEndDate <= EndDate)
.Where(x => x.Shift.LocumClaimTF == locumClaimTF1 || x.Shift.LocumClaimTF == locumClaimTF2)
.Where(x => x.Shift.organisationID == OrgID)
.GroupBy(s => s.assignedLocumID)
.Select(g => new dataRowDTO { dataLabel = string.Concat(g.FirstOrDefault().User.FullName), dataCount = g.Count(), dataCurrencyAmount = g.Sum(sd => sd.shiftDateTotal.Value) }
).Sum(g=>g.dataCurrencyAmount);
}
else // Ignore OrgID - Get ALL Orgs
{
return _UoW.ShiftDates.Get(x => x.shiftStartDate >= StartDate && x.shiftEndDate <= EndDate)
.Where(x => x.Shift.LocumClaimTF == locumClaimTF1 || x.Shift.LocumClaimTF == locumClaimTF2)
.GroupBy(s => s.assignedLocumID)
.Select(g => new dataRowDTO { dataLabel = string.Concat(g.FirstOrDefault().User.FullName), dataCount = g.Count(), dataCurrencyAmount = g.Sum(sd => sd.shiftDateTotal.Value) }
).Sum(g => g.dataCurrencyAmount);
}
I'm using EF with unit of work pattern to get data frm
A few things come to mind, from top to bottom:
For handling the Booleans
bool locumClaimTF1 = (reportType == 1 || reportType == 2);
bool locumClaimTF2 = (reportType == 1);
From what I read in the query though, if the report type is 1 or 2, you want the Shift's LocumClaimTF flag to have to be True. If that is the case, then you can forget the Boolean flags and just use the reportType in your condition.
Next, for composing the query, you can conditionally compose where clauses. This is a nice thing about the fluent Linq syntax. However, let's start temporarily with a regular DbContext rather than the UoW because that will introduce some complexities and questions that you'll need to look over. (I will cover that below)
using (var context = new ApplicationDbContext()) // <- insert your DbContext here...
{
var query = context.ShiftDates
.Where(x => x.shiftStartDate >= StartDate
&& x.shiftEndDate <= EndDate);
if (reportType == 1 || reportType == 2)
query = query.Where(x.Shift.LocumClaimTF);
if (OrgId > 0)
query = query.Where(x => x.Shift.organisationID == OrgID);
var total = query.GroupBy(s => s.assignedLocumID)
.Select(g => new dataRowDTO
{
dataLabel = tring.Concat(g.FirstOrDefault().User.FullName),
dataCount = g.Count(),
dataCurrencyAmount = g.Sum(sd => sd.shiftDateTotal.Value)
})
.Sum(g=>g.dataCurrencyAmount);
}
Now this here didn't make any sense. Why are you going through the trouble of grouping, counting, and summing data, just to sum the resulting sums? I suspect you've copied an existing query that was selecting a DTO for the grouped results. If you don't need the grouped results, you just want the total. So in that case, do away with the grouping and just take the sum of all applicable records:
var total = query.Sum(x => x.shiftDateTotal.Value);
So the whole thing would look something like:
using (var context = new ApplicationDbContext()) // <- insert your DbContext here...
{
var query = context.ShiftDates
.Where(x => x.shiftStartDate >= StartDate
&& x.shiftEndDate <= EndDate);
if (reportType == 1 || reportType == 2)
query = query.Where(x.Shift.LocumClaimTF);
if (OrgId > 0)
query = query.Where(x => x.Shift.organisationID == OrgID);
var total = query.Sum(x => x.shiftDateTotal.Value);
return total;
}
Back to the Unit of Work: The main consideration when using this pattern is ensuring that this Get call absolutely must return back an IQueryable<TEntity>. If it returns anything else, such as IEnumerable<TEntity> then you are going to be facing significant performance problems as it will be returning materialized lists of entities loaded to memory rather than something that you can extend to build efficient queries to the database. If the Get method does not return IQueryable, or contains methods such as ToList anywhere within it followed by AsQueryable(), then have a talk with the rest of the dev team because you're literally standing on the code/EF equivalent of a land mine. If it does return IQueryable<TEntity> (IQueryable<ShiftDate> in this case) then you can substitute it back into the above query:
var query = _UoW.ShiftDates.Get(x => x.shiftStartDate >= StartDate && x.shiftEndDate <= EndDate);
if (reportType == 1 || reportType == 2)
query = query.Where(x.Shift.LocumClaimTF);
if (OrgId > 0)
query = query.Where(x => x.Shift.organisationID == OrgID);
var total = query.Sum(x => x.shiftDateTotal.Value);
return total;