Get total count of days between two dates Linq C# - entity-framework

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();

Related

Create subquery for orderby

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.

How to optimize the query - Ef core

I am working a asp .net core web API with EF core.
I wrote this query. But this take 20-30seconds to execute.
Anyone have idea to improve this query.
var hotels = await _context.Hotels
.Where(i => (i.DestinationCode == request.Destination))
.Select(i => new HotelListHotelVm
{
Item1 = i.Item1,
Item2 = i.Item2,
Item3 = i.Item3,
Item4Code = i.Item4Code,
Item4Description = i.Item4.TypeDescription,
Item5 = i.Item5.Select(x => new HotelListHotelVm.HotelListItem5Vm
{
Code = x.Item5Code,
Description = x.Item5.Description,
}).Where(x =>(incomingItem5s.Length > 0 ) ? (incomingItem5s.Contains(x.Code)) : (x.Code != "")),
Item6 = i.Item6.Select(x => new HotelListHotelVm.HotelListHotelItem6Vm
{
Id = x.Id,
Item6TypeCode = x.Item6TypeCode,
Order = x.Order,
Path = x.Path,
VisualOrder = x.VisualOrder,
}).Take(3),
HotelFacilities = i.Facilities.ToList().Distinct().Take(6).Select(x => new HotelListHotelVm.HotelListFacilityVm {
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode
}),
})
.Where( i => ((incomingItem4.Length > 0 ) ? (incomingItem4.Contains(i.Item4Code)) : (i.Item4Code != "")) )
.OrderByDescending(i => i.Code)
.PaginatedListAsync(request.PageNumber, request.PageSize);
foreach( var item in hotels.Items){
foreach(var facility in item.HotelFacilities){
foreach( var fac in _context.Facilities){
if(facility.FacilityCode == fac.Code){
facility.HotelFacilityDescription = fac.Description;
}
}
}
}
I f I remove those foreach code, The query takes 8-10s to execute.
But I need those foreach codes. Because I need the HotelFacilityDescription
Any suggestion for optimize the query ?
Edit The i.Facilities - model
public class HotelFacility
{
// removed some
public int FacilityCode { get; set; }
public int FacilityGroupCode { get; set; }
public FacilityGroup FacilityGroup { get; set; }
public int HotelCode { get; set; }
public Hotel Hotel { get; set; }
}
}
_context.Facilities will be enumerated (i.e. database will be called) for every iteration of previous loops. The quick fix is to call it ones and store results in variable:
var facilities = _context.Facilities.ToList();
foreach( var item in hotels.Items){
foreach(var facility in item.HotelFacilities){
foreach(var fac in facilities){
if(facility.FacilityCode == fac.Code){
facility.HotelFacilityDescription = fac.Description;
}
}
}
}
Next improvement can be converting facilities into Dictionary for searching purposes.
Even better approach can be writing query joining with _context.Facilities on database side (but here more info needed).
I've read this a couple times, but it looks like the relationship for Hotel.Facilities is a Facility, so could you not just do:
HotelFacilities = i.Facilities.ToList().Distinct().Take(6).Select(x => new HotelListHotelVm.HotelListFacilityVm {
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode,
HotelFacilityDescription = x.Description
}),
If for some reason Hotel.Facilities is not pointing at a Facility, but is a Many-to-Many HotelFacilityGroup entity to a FacilityGroup, that also contains a FacilityCode, if the associated FacilityGroup has access to a set of Facilities beneath it you could leverage that:
Edit: It sounds like multiple Facilities share the same Code where some may have a null description. Provided that the facilities matching the code would be within the same facility group and not consider the same Code within different facility groups. If you need to match the code across all facilities then there probably isn't much of an alternative to loading the entire set of facility codes & descriptions.
HotelFacilities = i.Facilities.ToList().Distinct().Take(6).Select(x => new HotelListHotelVm.HotelListFacilityVm {
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode,
HotelFacilityDescription = x.FacilityGroup.Facilities.Where(f => f.Code == x.FacilityCode && f.Description != null).Select(f => f.Description).FirstOrDefault()
}),
That would avoid the need to go load all of the facilities to resolve that code. Otherwise, if you do need to fetch across all facilities, pre-loading them would be the way to go, but rather than fetching the entire Facility entity I would recommend just the values you need, the Code and the Description. This cuts down on the amount of memory needed and potentially be a faster query:
var facilities = _context.Facilities
.Select(f => new
{
f.Code,
f.Description
}).ToList();
Edit:
From there, finding a match using:
foreach( var facility in hotels.Items.SelectMany(x => x.HotelFacilities)
{
facility.HotelFaciltyDescription = facilities
.Where(x => x.Code == facility.FacilityCode
&& !string.IsNullOrEmpty(x.Description)
.Select(x => x.Description)
.FirstOrDefault();
}
I would recommend an OrderBy clause to ensure the selection of the facility is predictable as it sounds like there could be multiple matches on a code with a non-null description.
The loop can be eliminated by projecting the value in the LINQ to Entities query.
It would have been quite easy if you had relationship and navigation property like other *Code fields. But as clarified in the comments, there is no such relationship, so you have to resort to old good manual left other join to emulate what navigation property provide automatically, e.g.
HotelFacilities = i.Facilities.ToList().Distinct().Take(6)
// left outer join with Facilities
.SelectMany(x => _context.Facilities
.Where(f => x.FacilityCode == f.Code).DefaultIfEmpty(),
(x, x_Facility) => new HotelListHotelVm.HotelListFacilityVm
{
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode,
HotelFacilityDescription = x_Facility.Description // <--
}),
Here x_Facility emulates optional reference navigation property x.Facility if existed.
In case you need just single property from the related table, instead of a left join you could also use the original query with single value returning correlated subquery inside the projection, e.g.
HotelFacilities = i.Facilities.ToList().Distinct().Take(6)
.Select(x => new HotelListHotelVm.HotelListFacilityVm
{
Id = x.Id,
FacilityGroupCode = x.FacilityGroupCode,
HotelFacilityGroupDescription = x.FacilityGroup.Description,
FacilityCode = x.FacilityCode,
HotelFacilityDescription = _context.Facilities
.Where(f => x.FacilityCode == f.Code)
.Select(f => f.Description)
.FirstOrDefault() // <--
}),
or even
HotelFacilityDescription = _context.Facilities
.FirstOrDefault(f => x.FacilityCode == f.Code).Description
All these will eliminate the need of the post loop executiong additional database queries. You can test them and take the one with best performance (#2 and #3 produce one and the same SQL, so it's a matter of taste - the choice is between #1 and #2/3).

Cleanest way to implement multiple parameters filters in a REST API

I am currently implementing a RESTFUL API that provides endpoints to interface with a database .
I want to implement filtering in my API , but I need to provide an endpoint that can provide a way to apply filtering on a table using all the table's columns.
I've found some patterns such as :
GET /api/ressource?param1=value1,param2=value2...paramN=valueN
param1,param2...param N being my table columns and the values.
I've also found another pattern that consists of send a JSON object that represents the query .
To filter on a field, simply add that field and its value to the query :
GET /app/items
{
"items": [
{
"param1": "value1",
"param2": "value",
"param N": "value N"
}
]
}
I'm looking for the best practice to achieve this .
I'm using EF Core with ASP.NET Core for implementing this.
Firstly be cautious about filtering on everything/anything. Base the available filters on what users will need and expand from that depending on demand. Less code to write, less complexity, fewer indexes needed on the DB side, better performance.
That said, the approach I use for pages that have a significant number of filters is to use an enumeration server side where my criteria fields are passed back their enumeration value (number) to provide on the request. So a filter field would comprise of a name, default or applicable values, and an enumeration value to use when passing an entered or selected value back to the search. The requesting code creates a JSON object with the applied filters and Base64's it to send in the request:
I.e.
{
p1: "Jake",
p2: "8"
}
The query string looks like:
.../api/customer/search?filters=XHgde0023GRw....
On the server side I extract the Base64 then parse it as a Dictionary<string,string> to feed to the filter parsing. For example given that the criteria was for searching for a child using name and age:
// this is the search filter keys, these (int) values are passed to the search client for each filter field.
public enum FilterKeys
{
None = 0,
Name,
Age,
ParentName
}
public JsonResult Search(string filters)
{
string filterJson = Encoding.UTF8.GetString(Convert.FromBase64String(filters));
var filterData = JsonConvert.DeserializeObject<Dictionary<string, string>>(filterJson);
using (var context = new TestDbContext())
{
var query = context.Children.AsQueryable();
foreach (var filter in filterData)
query = filterChildren(query, filter.Key, filter.Value);
var results = query.ToList(); //example fetch.
// TODO: Get the results, package up view models, and return...
}
}
private IQueryable<Child> filterChildren(IQueryable<Child> query, string key, string value)
{
var filterKey = parseFilterKey(key);
if (filterKey == FilterKeys.None)
return query;
switch (filterKey)
{
case FilterKeys.Name:
query = query.Where(x => x.Name == value);
break;
case FilterKeys.Age:
DateTime birthDateStart = DateTime.Today.AddYears((int.Parse(value) + 1) * -1);
DateTime birthDateEnd = birthDateStart.AddYears(1);
query = query.Where(x => x.BirthDate <= birthDateEnd && x.BirthDate >= birthDateStart);
break;
}
return query;
}
private FilterKeys parseFilterKey(string key)
{
FilterKeys filterKey = FilterKeys.None;
Enum.TryParse(key.Substring(1), out filterKey);
return filterKey;
}
You can use strings and constants to avoid the enum parsing, however I find enums are readable and keep the sent payload a little more compact. The above is a simplified example and obviously needs error checking. The implementation code for complex filter conditions such as the age to birth date above would better be suited as a separate method, but it should give you some ideas. You can search for children by name, and/or age, and/or parent's name for example.
I have invented and found it useful to combine a few filters into one type for example CommonFilters and make this type parseable from string:
[TypeConverter(typeof(CommonFiltersTypeConverter))]
public class CommonFilters
{
public PageOptions PageOptions { get; set; }
public Range<decimal> Amount { get; set; }
//... other filters
[JsonIgnore]
public bool HasAny => Amount.HasValue || PageOptions!=null;
public static bool TryParse(string str, out CommonFilters result)
{
result = new CommonFilters();
if (string.IsNullOrEmpty(str))
return false;
var parts = str.Split(new[] { ' ', ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var part in parts)
{
if (part.StartsWith("amount:") && Range<decimal>.TryParse(part.Substring(7), out Range<decimal> amount))
{
result.Amount = amount;
continue;
}
if (part.StartsWith("page-options:") && PageOptions.TryParse(part.Substring(13), out PageOptions pageOptions))
{
result.PageOptions = pageOptions;
continue;
}
//etc.
}
return result.HasAny;
}
public static implicit operator CommonFilters(string str)
{
if (TryParse(str, out CommonFilters res))
return res;
return null;
}
}
public class CommonFiltersTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
if (sourceType == typeof(string))
{
return true;
}
return base.CanConvertFrom(context, sourceType);
}
public override object ConvertFrom(ITypeDescriptorContext context,
CultureInfo culture, object value)
{
if (value is string str)
{
if (CommonFilters.TryParse(str, out CommonFilters obj))
{
return obj;
}
}
return base.ConvertFrom(context, culture, value);
}
}
the request looks like this:
public class GetOrdersRequest
{
[DefaultValue("page-options:50;amount:0.001-1000;min-qty:10")]
public CommonFilters Filters { get; set; }
//...other stuff
}
In this way you reduce the number of input request parameters, especially when some queries don't care about all filters
If you use swagger map this type as string:
c.MapTypeAsString<CommonFilters>();
public static void MapTypeAsString<T>(this SwaggerGenOptions swaggerGenOptions)
{
swaggerGenOptions.MapType(typeof(T), () => new OpenApiSchema(){Type = "string"});
}

how to get list of one entity set from another entity set in a many to many relationship

1I am working with controller code in MVC.
I have the follow code working where the are related entities priority list based on teams:
public JsonResult GetPrioritiesByTeam(int id)
{
List<Priority> priorities = new List<Priority>();
if (id > 0)
{
priorities = db.Priorities.Where(p => p.Team == id ).ToList();
}
else
{
priorities.Insert(0, new Priority { Id = 0, PriorityDescription = "--Select a Team first--" });
}
var result = (from r in priorities
select new
{
id = r.Id,
name = r.PriorityDescription
}).ToList();
return Json(result, JsonRequestBehavior.AllowGet);
}
The statement
priorities = db.Priorities.Where(p => p.Team == id
does get me the list of priorities based on the team passed in by the parameter id.
I am now try to achieve the same successful result with another entity that is related to team- an entity called 'members'. However the relationship is not a one to many. Its a many to many- so I have an intermediate entity call 'teammembers'.
The Team is still the entity driving the choice. When the team is chosen, I need it to give me all the team member entries in teammembers, and then I need similar to the priorities code above, give me a members list which is based on the teammembers identified based on the team selected.
I think based on the code provided the else clause would be similar coding along with the var result coding.
I just cant seem to get the coding right for the clause in the condition for id >0.
How do I write that clause for the many to many so as to get the list of members for the team through teammembers?
You can assume entity team is Id and TeamDescription, entity Member is Id and MemberName, and entity teammember is an ID,and teamId and MemberId -just a straightforward many to many.
Been trying with LINQ method syntax and LINQ query syntax but cant get it working.
Thanks for any help you can provide
New code would look like the following except i need working id>0 clause:
public JsonResult GetMembersByTeam(int id)
{
List<Member> members = new List<Member>();
if (id > 0)
{
**members = Members.Where(needed clause).ToList();**
}
else
{
members.Insert(0, new Member { Id = 0, MemberFullName = "--Select a Team first--" });
}
var result = (from m in members
select new
{
id = m.Id,
name = m.MemberFullName
}).ToList();
return Json(result, JsonRequestBehavior.AllowGet);
}
Entity Diagram
Current code with suggested changes
public JsonResult GetMembersByTeam(int id)
{
List<Member> members = new List<Member>();
if (id > 0)
{
//members = db.Teams.Where(p => p.Id == id)
// .SelectMany(e => e.TeamMembers)
// .Select(e => e.Member)
// .ToList();
members = db.TeamMembers.Where(p => p.Team == id)
.Select(e => e.Member)
.ToList();
}
else
{
members.Insert(0, new Member { Id = 0, MemberFullName = "--Select a Team first--" });
}
var result = (from m in members
select new
{
id = m.Id,
name = m.MemberFullName
}).ToList();
return Json(result, JsonRequestBehavior.AllowGet);
}
results in error :
Cannot implicitly convert type 'System.Collections.Generic.List' to 'System.Collections.Generic.List,ManageHR5.model.Member>'
try this:
members = db.TeamMembers.Where(p => p.Team == id )
.Select(e => e.Member1)
.ToList();

NEWBIE : Selecting 2 or more information from IEnumerable Select

Newbie here, I have a problem with my code. Before this code returns an array of string (student name) but I need to add the student id without requiring me to recode the entire method. Can someone help me on how to do it? Basically I need to include the Student ID based on the Student Name.
TIA.
public Student[] GetAllStudents(string subject)
{
Student[] students = cache.GetAllStudents(subject);
if (students == null)
{
Subjects group = RetrieveSubjects(subject);
if (group != null)
{
students = group.Students.Select(r => r.StudentName).ToArray();
// I need to include also the Student ID based on the Student Name queried above.
}
else
{
students = new string[0];
}
cache.AddAllStudents(subject, students);
}
return students;
}
You can use anonymous class in your select clause, ie
students = group.Students
.Select(r => new { name= r.StudentName, id= r.StudentID } )
.ToArray();
Or just select Student object as it should has all you need
students = group.Students.ToArray();
// doing .Select(r=>r) is redundant and can be omited
Seem that you need to create Student class from your studentinfo class, you would want to change prop's in initializer.
students = group.Students
.Select(r => new Student {
StudentName= r.StudentName,
StudentID= r.StudentID
})
.ToArray();