SQL to entities - complex query selecting dates - c#

I'm using Entity Framework code-first and I want to query the appointments table in my database selecting:
All the appointments on given dates
public IEnumerable<Appointment> GetAppointments(IEnumerable<DateTime> datesWithEverything)
{
using (OneClickContext context = new OneClickContext())
{
var query = from e in context.AppointmentSet
where datesWithEverything.Contains(e.StartDate)
select e;
return query;
}
}
I also need only one appointment from any other day, the first found for each day not included in the given dates.
The reason for this, is that my calendar component will be able to show in bold the days containing at least one appointment, but I don't want to load thousands of appointments since I will be looking few days (with all the details) per request.
Is this possible hitting the database once?
Any help is appreciated.

If I understand your needs correctly, you are getting all appointment data for a range of dates, and then you simply need to know all other dates that have at least one appointment in order to bold them. You aren't actually using the appointment data on those other dates just yet. Is that correct?
You are right to try to limit the number of trips to the database, but you need to balance that with not returning more data than you actually need. If I understand right and all you need are dates with appointments, you should use a second query:
public IEnumerable<DateTime> GetDatesWithAppointments()
{
using (OneClickContext context = new OneClickContext())
{
var query = (from e in context.AppointmentSet
order by e.StartDate
select e.StartDate).Distinct();
return query.ToList();
}
}
Also, it's a matter of style but the variable "query" is not needed, and you can simply:
return (from e in context.AppointmentSet
order by e.StartDate
select e.StartDate).Distinct().ToList();
Note the ToList(). Linq query execution is deferred until the first time it is enumerated and your query context is declared in a using block. You must execute the query before the using block exits in this case.
On edit, since you require an actual appointment, you should be able to get what you want in a single trip using linq's union and grouping. Union will automatically remove any duplicate entries.
public IEnumerable<Appointment> GetAppointments(IEnumerable<DateTime> datesWithEverything)
{
using (OneClickContext context = new OneClickContext())
{
var query = from e in context.AppointmentSet
where datesWithEverything.Contains(e.StartDate)
select e;
query = query.Union(from e in context.AppointmentSet
group e by DbFunctions.TruncateTime(e.StartDate) into grp
select grp.FirstOrDefault());
return query.ToList();
}
}

Related

How to group by records from database based on date (day) c#?

I know this could be a possible duplicate question, pardon me if it is.
Is there a way to GroupBy all the records from the database by date?
So:
say i have multiple records for this date 22/05/2022
and say i have multiple records from this date: 23/05/2022
Can i group all the records based on date parameter 22/05 and 23/05?
So that i would end up with a list containing n list for each day.
Here is what i did:
var grpQuery = await ctx.Registration.GroupBy(c => c.DateReference.Day).ToListAsync();
Where:
Registration is my table from where i am pulling the data
DateReference is a Date object containing the date
But i am getting this error "the linq expession could not be translated".
Can somone give me some advice on this?
EDIT
I tried this but it seems not to load any data, even setting break a break point will not return anything:
var grpQuery = await query.GroupBy(d => new { DateReference = d.DateReference.Date }).Select(c => new RegistrationViewModel()
{
RegistrationId = c.FirstOrDefault().RegistrationId,
PeopleId = c.FirstOrDefault().PeopleId,
DateReference = c.Key.DateReference,
DateChange = c.FirstOrDefault().DateChange,
UserRef = c.FirstOrDefault().UserRef,
CommissionId = c.FirstOrDefault().CommissionId,
ActivityId = c.FirstOrDefault().ActivityId,
MinuteWorked = c.FirstOrDefault().MinuteWorked,
}).OrderBy(d => d.DateReference).ToListAsync();
Where:
RegistrationViewModel contains all those properties including DateReference
If i call the method using the API is stuck at "pending"
First, don't. Even if the query was fixed, the equivalent query in the database would be GROUP BY DATEPART(day,registration.Date) which can't use indexes and therefore is slow.
According to the docs the equivalent of DATEPART(day, #dateTime) is dateTime.Day. The query still needs to have a proper Select though.
A correct query would be :
counts = ctx.Registrations.GroupBy(r=>r.RegistrationDate.Day)
.Select(g=>new {Day=g.Key,Count=g.Count())
.ToList();
The equivalent, slow query would be
SELECT DATEPART(day,registration.Date) as Day,count(*)
FROM Registrations
GROUP BY DATEPART(day,registration.Date)
Things get worse if we have to eg filter by date. The query would have to scan the entire table because it wouldn't be able to use any indexes covering the Date column
SELECT DATEPART(day,registration.Date) as Day,count(*)
FROM Registrations
WHERE Date >'20220901'
GROUP BY DATEPART(day,registration.Date)
Imagine having to scan 10 years of registrations only to get the current month.
This is a reporting query. For date related reports, using a prepopulated Calendar table can make the query infinitely easier.
SELECT Calendar.Day,COUNT(*)
FROM Registrations r
INNER JOIN Calendar on r.RegistrationDate=Calendar.Date
GROUP BY Calendar.Day
or
SELECT Calendar.Year, Calendar.Semester, Calendar.Day,COUNT(*)
FROM Registrations r
INNER JOIN Calendar on r.RegistrationDate=Calendar.Date
WHERE Calendar.Year = #someYear
GROUP BY Calendar.Year, Calendar.Semester,Calendar.Day
A Calendar table or Date dimension is a table with prepopulated dates, years, months, semesters or any other reporting period along with their names or anything needed to make reporting easier. Such a table can contain eg 10 or 20 years of data without taking a lot of space. To speed up queries, the columns can be aggressively indexed without taking too much extra space.
Doing the same in EF Core requires mapping Calendar as an entity and performing the JOIN in LINQ. This is one of the cases where it makes no sense to add a relation between entities :
var query=from registration in ctx.Registrations
join date in Calendar
on registration.Date equals Calendar.Date
group registration by date.Day into g
select new { Day=g.Key, Count=g.Count()};
var counts = query.ToList();
If you are using EF Core Please try this:
var grpQuery = await ctx.Registration.Select(a=>new {Re = a, G = (EF.Functions.DateDiffDay(a.End,DateTime.Today))}).ToListAsync().ContinueWith(d=>d.Result.GroupBy(a=>a.G));

Handle Linq queries outputs

I've been struggling for the last 3 days on that topic.
I'm sure i'm doing something wrong but there, i need help.
During the load of a form, i'm doing a Linq query (on a global dataset) to populate fields on that form. As i want to be able to change the views of the form, i want queries that will make the data available in a specific format (to avoid having to query every now on then (the dataset is 20,000 lines)).
so i came up with that first queries :
var results =
from row in Globals.ds.Tables["Song"].AsEnumerable()
group row by (row.Field<int>("year"), row.Field<int>("rating")) into grp
orderby grp.Key
select new
{
year = grp.Key.Item1,
conte = grp.ToList().Count,
rating = grp.Key.Item2,
duree = grp.Sum(r => r.Field<int>("duree"))
};
It works and i'm pasting the result in the following screenshot (conte is the count)
Result of the query
1 have 2 issues :
1/ I really dont know how to handle that result : i would like to filter for a specific year and list all the subsequent ratings (i have from 1 to 6 per year). I tried the .ToList() but it only helped to get the count. The CopyToDataTable is not available for the query.
2/ i have buttons in the form that will need to access to that query, yet the var result is only available in the load and i can't manage to declare it at the class level.
Thanks for the help :)
So:
Your first point have been answered by #jdweng
It is possible to use LinQ also for collections (ex. List), not only Db queries.
The reason is that the result of the query is an anonymous type, and it can't be declared outside local scope. You must create a new class with the same structure.
public class MyResultClass
{
public int year;
public int conte;
public int rating;
public int duree;
}
Define your field:
List<MyResultClass> data;
And then use both:
var result =
from row in Globals.ds.Tables["Song"].AsEnumerable()
group row by (row.Field<int>("year"), row.Field<int>("rating")) into grp
orderby grp.Key
select new MyResultClass
{
year = grp.Key.Item1,
conte = grp.ToList().Count,
rating = grp.Key.Item2,
duree = grp.Sum(r => r.Field<int>("duree"))
};
data = result.ToList();
I hope I was helpful.

Sorting using C# , LINQ

I have a list of items in a SharePoint which has Title, Date and Priority. I would like to sort this collection on my client application while displaying it ,sorting need to first by Date Descending and then within a set of objects for a date, it should sort objects by Priority.
I am using below LINQ code, which is not providing the expected result. Means the below code not sorting object by priority with in a date.
var results = spObjects.OrderByDescending(n => n.Date).ThenBy(t => t.Priority);
Unsorted collection is as below
Sorted Collection - Expectation
I am using SharePoint CAML query, since I am fetching these object from SP and I found it difficult to do it in CAML, but I assume this can be done using LINQ on C# side, Isnt it ? Could anyone please help ?
I'll bet you that n.Date is a DateTime object, with hours, minutes, and seconds. Your Linq looks good, so you probably have different times that are causing items later in the day with lower priorities to be sorted before items earlier in the day.
Try something like:
var results = spObjects.OrderByDescending(n => new DateTime(n.Date.Year, n.Date.Month, n.Date.Day)).ThenBy(t => t.Priority);
If I understand you correctly you want a nested collection under a date heading, you might do something like this...
var results = from so in spObjects
orderby so.Date descending
select new {
Date = so.Date,
Items = (from so2 in spObjects
where so2.Date == so.Date
orderby so2.Priority
select new {
so2.Title,
so2.Priority
})
};

Most recent records with 2 tables and take / skip

What I want to do, is basically what this question offers: SQL Server - How to display most recent records based on dates in two tables .. Only difference is: I am using Linq to sql.
I have to tables:
Assignments
ForumPosts
These are not very similar, but they both have a "LastUpdated" field. I want to get the most recent joined records. However, I also need a take/skip functionality for paging (and no, I don't have SQL 2012).
I don't want to create a new list (with ToList and AddRange) with ALL my records, so I know the whole set of records, and then order.. That seems extremely unefficient.
My attempt:
Please don't laugh at my inefficient code.. Well ok, a little (both because it's inefficient and... it doesn't do what I want when skip is more than 0).
public List<TempContentPlaceholder> LatestReplies(int take, int skip)
{
using (GKDBDataContext db = new GKDBDataContext())
{
var forumPosts = db.dbForumPosts.OrderBy(c => c.LastUpdated).Skip(skip).Take(take).ToList();
var assignMents = db.dbUploadedAssignments.OrderBy(c => c.LastUpdated).Skip(skip).Take(take).ToList();
List<TempContentPlaceholder> fps =
forumPosts.Select(
c =>
new TempContentPlaceholder()
{
Id = c.PostId,
LastUpdated = c.LastUpdated,
Type = ContentShowingType.ForumPost
}).ToList();
List<TempContentPlaceholder> asm =
assignMents.Select(
c =>
new TempContentPlaceholder()
{
Id = c.UploadAssignmentId,
LastUpdated = c.LastUpdated,
Type = ContentShowingType.ForumPost
}).ToList();
fps.AddRange(asm);
return fps.OrderBy(c=>c.LastUpdated).ToList();
}
}
Any awesome Linq to SQl people, who can throw me a hint? I am sure someone can join their way out of this!
First, you should be using OrderByDescending, since later dates have greater values than earlier dates, in order to get the most recent updates. Second, I think what you are doing will work, for the first page, but you need to only take the top take values from the joined list as well. That is if you want the last 20 entries from both tables combined, take the last 20 entries from each, merge them, then take the last 20 entries from the merged list. The problem comes in when you attempt to use paging because what you will need to do is know how many elements from each list went into making up the previous pages. I think, your best bet is probably to merge them first, then use skip/take. I know you don't want to hear that, but other solutions are probably more complex. Alternatively, you could take the top skip+take values from each table, then merge, skip the skip values and apply take.
using (GKDBDataContext db = new GKDBDataContext())
{
var fps = db.dbForumPosts.Select(c => new TempContentPlaceholder()
{
Id = c.PostId,
LastUpdated = c.LastUpdated,
Type = ContentShowingType.ForumPost
})
.Concat( db.dbUploadedAssignments.Select(c => new TempContentPlaceholder()
{
Id = c.PostId,
LastUpdated = c.LastUpdated,
Type = ContentShowingType.ForumPost
}))
.OrderByDescending( c => c.LastUpdated )
.Skip(skip)
.Take(take)
.ToList();
return fps;
}

Getting Entity Framework to eager load on Group By

I know that changing the shape of a query causes Entity Framework to ignore the include calls but is there a way I can get it to load the sub properties when I do a select many and a group by. In the following example I want to notify all the employees who have a job booked in a certain time period. Calling .ToArray() after the where only hits the database once but I am doing the SelectMany and GroupBy in memory. Is there a way I can get the SelectMany and the GroupBy to happen on the SQL server and still include the ServiceType and Ship and the Employee details?
I am looking for a way to make one SQL call to the database and end up with a list of Employees who have a job in the time period and the jobs they are assigned to.
var employeeJobs = DataContext.Jobs.
Include("ServiceType").
Include("Ship").
Include("JobEmployees.Employee").
Where(j => j.Start >= now && j.Start <= finish).
OrderBy(j => j.Start).
ToArray().
SelectMany(j => j.JobEmployees, (j, je) => new {
Job = j,
Employee = je.Employee
}).GroupBy(j => j.Employee);
The following should work:
var q = from e in DataContext.Employees
where e.Job.Start > ....
order by e.Job.Start
select new {Employee = e, Job = e.Job, Ship = e.Job.Ship, ServiceType = e.Job.ServiceType}; // No db hit yet.
var l = q.GroupBy(item=>item.Employee) // no db hit yet.
.ToList(); // This one causes a db hit.
why don't you create a view and then reference this from the EF?
, this also has the added benefit of the database server doing the work, rather than the app server.
Trying move the Include()'s to the very end after your groupby.

Categories

Resources