LINQ: how to get 1 month records - c#

The following query is returning a number of daily orders. I want to create another query that will return int number of orders in one month. The problem is some months have 29days, some 30 or 31. Any help is appreciated!
public int GetNewDailyOrders()
{
return _DbContext.Carts.Where(x => x.Created >= DateTime.UtcNow.AddDays(-1)).Count();
}

public int GetMonthlyOrders(int month, int year)
{
return _DbContext.Carts.Count(x => x.Created.Year == year && x.Created.Month == month);
}
may be, you should consider support different timezones, summertime or not, something like this as well

My chief complaint with Nobody's answer is that it manipulates table data, which generally kills the db's ability to use indexes
Consider instead working out the date range and querying it instead:
var n = DateTime.Now;
var f = new DateTime(n.Year, n.Month, 1);
var t = new DateTime(n.Year, n.Month + 1, 1);
_dbContext.Carts.Count(c => c.CreatedDate >= f && c.CreatedDate < t);

Related

LINQ: latest sum of data within data range

I have a snapshot table in my database that holds historical data of appointment availability.
I'm trying to write a LINQ query to get the total number of AvailableSlots within a given date range from the latest snapshot.
Here is what my table looks like:
So with this given data, I'd want my LINQ query to return the sum of AvailableSlots within the date range of 2018-01-01 - 2018-01-02 and with the latest SnapShotTime. So, I'd expect the query to return 4.
Here is what I have so far.
var test = db.snapshots
.GroupBy(g =>
g.AppointmentTime >= startDate &&
g.AppointmentTime <= endDate
).Select(s => s.OrderByDesending(x => x.SnapShotTime).FirstOrDefault();
However, I'm not sure how to put the Sum of available slots into this LINQ query. Any help on writing this query would be appreciated!
i dont see exactly the query that you wrote do, but based on your explanation i think sth like this might have work
var query=db.snapshots
.Where(x=>x.AppointmentTime >= startDate &&
x.AppointmentTime <= endDate)
.GroupBy(x=>x.SnapShotTime)
.OrderByDesending(g=>g.Key)
.Take(1)
.Sum(x=>x.Value.AvailableSlots);
or if it seems so complicated you better first get the latest date like this
var latest=db.snapshots
.OrderByDesending(x => x.SnapShotTime)
.FirstOrDefault().SnapShotTime;
and then get your count like this
var query=db.snapshots
.Where(x=>x.AppointmentTime >= startDate &&
x.AppointmentTime <= endDate &&
x.SnapShotTime==latest)
.Sum(x=>x.AvailableSlots);
Here is what I did.
static void Main(string[] args)
{
DateTime startDate = new DateTime();
DateTime endDate = new DateTime();
List<FakeAppointments> appointmentsFromDatabase = new List<FakeAppointments>();
var appointmentsBetweenStartDateAndEndDate = appointmentsFromDatabase.Where(p => p.SnapshotTime >= startDate && p.SnapshotTime <= endDate).ToList();
int sum = appointmentsBetweenStartDateAndEndDate.Sum(p => p.AvailableSlots);
Console.ReadKey();
}
public class FakeAppointments
{
public DateTime SnapshotTime;
public int AvailableSlots;
}

How can I fill the gaps in attribute values in the list?

I have a list:
List<BtnCountViews> btnCountViewsList;
The BtnCountViews class looks like this:
public class BtnCountViews
{
public int DayOfYear { get; set; }
public int BtnCount { get; set; }
public int Views { get; set; }
}
I have a rather unusual requirement and I am not sure how to go about starting to implement it.
What I would like to do is to fill in the btnCountViewsList with `BtnCountViews for the missing DayOfYear with objects that have a BtnCount of 0 and Views of 0.
To give me a start can anyone tell me how I can find the min and max DayOfYear in the btnCountViewsList. Note I tagged this with LINQ but I'm not sure if this is the best tool to use.
Also would be happy if someone can suggest a way to fill in the missing objects but that's not really the focus of this question as I think I need to find out how to get the min and max first.
You can add missing days without finding min and max explicitly:
Sort the list by DayOfYear in ascending order (how?)
Start a loop index i at the end of the list, and work your way backward; stop when i reaches zero
Compare DayOfYear attribute at i and i-1
If the two days differ by one, move down to the next i
Otherwise insert a new record with DayOfYear set to that of btnCountViewsList[i] minus one.
At the end of this process your list would contain entries for each value of DayOfYear. Here is a sample implementation:
items.Sort((x, y) => x.DayOfYear.CompareTo(y.DayOfYear));
Console.WriteLine("Before: {0}", string.Join(", ", items.Select(x => x.DayOfYear)));
int i = items.Count-1;
while (i > 0) {
if (items[i].DayOfYear == items[i-1].DayOfYear+1) {
i--;
} else {
items.Insert(i, new BtnCountViews { DayOfYear = items[i].DayOfYear-1 });
}
}
Demo.
This is working on linqpad:
Int32 max = 0, min = 0;
btnCountViewsList.ForEach(x => {
min = Math.Min(x.Views, min);
max = Math.Max(x.Views, max);
});
What I would like to do is to fill in the btnCountViewsList with `BtnCountViews for the missing DayOfYear with objects that have a BtnCount of 0 and Views of 0.
My suggestion is that we don't try to find the missing days, we create all:
BtnCountViews[] arr = new BtnCountViews[365]; // or 366?
// suppose DayOfYear begins with 0.
for (int i = 0; i < arr.Length; i++)
{
arr[i] = new BtnCountViews { DayOfYear = i };
}
foreach (BtnCountViews item in btnCountViewsList)
{
arr[item.DayOfYear].BtnCount = item.BtnCount;
arr[item.DayOfYear].Views = item.Views;
}
then arr is what you want.
And if the result should be the btnCountViewsList:
btnCountViewsList.Clear();
btnCountViewsList.AddRange(arr);
So the lazy in me says, make a backfill list and use your existing (and gappy) list as a map.
public static IList<BtnCountViews> GetDefaultList()
{
var defaultList = Enumerable.Range(1, 365).Select(e =>
new BtnCountViews
{
DayOfYear = e,
BtnCount = 0,
Views = 0
}
).ToList();
return defaultList;
}
Iterate through the backfill list and consult the map to see if the DayOfYear value exists as a key, and if not, then add it to the map.
public static IList<BtnCountViews> GetBackFilledList(IList<BtnCountViews> incoming)
{
var map = incoming.ToDictionary(k => k.DayOfYear, v => v);
var defaultList = GetDefaultList();
foreach(var itm in defaultList)
{
if (map.ContainsKey(itm.DayOfYear)) continue;
map.Add(itm.DayOfYear, itm);
}
return map.Select(m => m.Value).ToList();
}
Once the iteration is finished, convert the map into a list, which should now consist of the original values + default values for missing DayOfYear entries as well.
return map.Select(m => m.Value).ToList();
Dotnetfiddle of a sample program here: https://dotnetfiddle.net/wSJy56
Is there a more elegant way to do this? Most surely. But this code executes in about 0.011 seconds, which to me is pretty decent so long as you're not calling this functionality over and over again (e.g. you decide to analyze 30 years of data and need to get that done in 0.011 seconds). But then we'd have to be looking more towards parallelism rather than code elegance to solve that can of worms.
Hope this helps...
Try the following
btnCountViewsList = btnCountViewsList.Where(b => b.BtnCount == 0).Where(v => v.Views == 0).ToList();
If I understood what you were asking, you want to get objects where BtnCount = 0 and Views = 0.
This will select all the objects where Views = 0, and then that IEnumarable will be through another LINQ expression where it only selects the other property that equals to 0.
The shortest linq way, using an left outer join (LEFT OUTER JOIN in LINQ) and Range
var result = (from a in Enumerable.Range(0, 365)
join lst in btnCountViewsList on a equals lst.DayOfYear into ps
from p in ps.DefaultIfEmpty()
select (p==null) ? new BtnCountViews() { DayOfYear = a}:p).ToList()
among the lines of some other responses, but without hard coding the total days of the year as leap years will have 366 days
var range = new
{
Start = new DateTime(2017, 1, 1),
End = new DateTime(2017, 12, 31),
};
var days = Enumerable.Range(range.Start.DayOfYear, range.End.DayOfYear);
var query = from day in days
from counter in
(
from temp in btnCountViewsList
where temp.DayOfYear == day
select temp
).DefaultIfEmpty()
select new BtnCountViews
{
DayOfYear = day,
BtnCount = counter == null ? 0 : counter.BtnCount,
Views = counter == null ? 0 : counter.Views,
};
will give you something like

Linq sum by variable number of monthS or weekS

SqlServer table contains records of time and hours burned, materials cost by vendors
[IdPK] [DateTime] [MaterialsCost] [Hours] [Vendor_FK] [Project_FK] [Lat] [Long]
The user decides how far back he wants to see totals and for which column value, i.e. he wants totals going back - X number of monthS, or X number of weekS on any columns_FK filter value.
For e.g. he wants totals for cost, hours on either, Vendor = Nike or ProjectX, going back (1) month, (3) weeks, or 2 months from a certain date. So, I'm trying to get the totals for based on a parameterized Linq query.
Question:
Using any of columns as a filter/selector in linq, how to get the Monthly/weekly totals for cost,hours - going back (X) number of months or weeks?
Also, should I write separate queries for months vs weeks, and separate queries for each column (columns value selected by the user, is passed to me as a genric val)?
// I tried this... but just stuck
DateTime now = DateTime.Now;
Int goBack = passedinGoingBack; // amount of units to go back
var backUnits = obj.GetType().GetProperty(name); // tried getting month or week??
var getRows = table.AsEnumerable()
var columnName = passedInColumnName;
var filter = passedInValue;
.Where(r => r.Field<DateTime>(columnName).Year == now.Year
&& r.Field<DateTime>(columnName).Month == now.Month);
This question is a little bit opinion-based. You can do it in any way it is convenient for you. That's how I would do this.
First: you can use Sum LINQ function for getting total according to the given summing rule.
var monthlyRows = table.AsEnumerable()
.Where(r => r.Field<DateTime>("ColumnName").Year == now.Year
&& r.Field<DateTime>("ColumnName").Month == now.Month);
var monthlyTotalForCost = monthlyRows.Sum(r => r.Field<decimal>("CostColumn"));
var monthlyTotalForHours = monthlyRows.Sum(r => r.Field<decimal>("HoursColumn"));
var weeklyRows = table.AsEnumerable()
.Where(r => r.Field<DateTime>("ColumnName").Year == now.Year
&& r.Field<DateTime>("ColumnName").Week == now.Week);
var weeklyTotalForCost = monthlyRows.Sum(r => r.Field<decimal>("CostColumn"));
var weeklyTotalForHours = monthlyRows.Sum(r => r.Field<decimal>("HoursColumn"));
Second: I would do separate queries for weekly and monthly totals as that' a sum of different entity values and there can be a different logic.
However, I would do a helping function for me like this:
public static class TableLinqHelper
{
public static SumOfTableColumn<T>(this IEnumerable<DataRow> rows, string columnName)
{
return rows.Sum(r => r.Field<T>(columnName));
}
public static DateTime GetDate(this DataRow row)
{
return row.Field<DateTime>("DateColumn");
}
public static GetTotalForCost(this IEnumerable<DataRow> row)
{
return SumOfTableColumn<decimal>(row, "CostColumn");
}
public static GetTotalForHours(this IEnumerable<DataRow> row)
{
return SumOfTableColumn<double>(row, "HoursColumn");
}
}
var monthlyRows = table.AsEnumerable()
.Where(r => r.GetTime().Year == now.Year
&& r.GetTime().Month == now.Month);
var weeklyRows = table.AsEnumerable()
.Where(r => r.GetTime().Year == now.Year
&& r.GetTime().Week == now.Week);
var monthlyTotalForCost = monthlyRows.GetTotalForCost();
var monthlyTotalForHours = monthlyRows.GetTotalForHours();
var weeklyTotalForCost = weeklyRows.GetTotalForCost();
var weeklyTotalForHours = weeklyRows.GetTotalForHours();
Update:
Filtering: You can filter your results using Where LINQ.
Dictionary<string, object> Filters = new Dictionary<string, object>();
Filters.Add("VendorColumn", "Nike");
Filters.Add("Hours", 7.0);
foreach (var filter in Filters)
{
monthlyRows = monthlyRows.Where(r => // ...);
}
Several monthes and weeks: You can change the condition of DateTime where.
int rowsToBeShown = 4;
var monthlyRows = table.AsEnumerable()
.Where(r => r.Field<DateTime>("ColumnName") > DateTime.Now.AddMonths(-rowsToBeShown));
It will show results for the last 4 monthes.
LINQ provides very convenient and flexible tools for data manipulation. It's all up to your fantasy.

LINQ Statement foreach hour in day issue

I have the following statement that is taking a long time to load. Can anyone advise me how i can solve this performance issue and still get the same result a count for each hour. I have to loop though each machine first and loop through each hour for each machine.
foreach (string MachineID in this.lboxMachines.SelectedItems)
{
if (this.lboxMachines.SelectedItems.Contains(GimaID))
{
{
for (int i = 0; i <= 23; i++)
{
var PartsCast = (from p in ProductionEntity.PARTDATAs
where p.DATE_TIME >= StartDate
where p.DATE_TIME <= EndDate
where p.MACHINE == MachineID
select p).Count();
StartDate.AddHours(1);
DT.Rows[row][col] = PartsCast;
col++;
}
}
}
}
Would i be better doing one statement for each machine or leave it how it is?
I believe you are having the code get things multiple times due to IQueryable nature of Linq which would be causing the slow down. Let us break it down into steps to see if we can lesson the impact.
One needs to nail down what is not changing by getting it into a list and away from IQueryable. In the example below, I am ignoring where the data is going, just giving you the processing needed and a structure to extract the info.
// Get the machines to process only once by not getting a queryable.
var machines =
this.lboxMachines.SelectedItems
.Where( machine => machine.Contains(GimaID) )
.ToList(); // Don't keep this IQueryable but as a hard list by this call.
// Get *only* the parts to use; using one DB call
var parts = ProductionEntity.PARTDATAs
.Where(part => machines.Contains(part.Machine))
.ToList();
// Now from the parts get the count based off of the time each hour
var resultPerHour =
Enumerable.Range(0, 24)
.Select (hour => new
{
Hour = hour,
Count = parts.Count(part => part.DATETIME >= StartDate.AdHours(hour) && part.DATETIME <= EnDate)
});
resultPerHour can now be reported to the user.
Note if parts result is too big for the memory, then remove the .ToList on it and use it as IQueryable.
Based on you code try this
if (this.lboxMachines.SelectedItems != null && this.lboxMachines.SelectedItems.Contains(GimaID))
{
foreach (string MachineID in this.lboxMachines.SelectedItems)
{
for (int i = 0; i <= 23; i++)
{
var PartsCast = (from p in ProductionEntity.PARTDATAs
where p.DATE_TIME >= StartDate
where p.DATE_TIME <= EndDate
where p.MACHINE == MachineID
select p).Count();
StartDate = StartDate.AddHours(1);
DT.Rows[row][col] = PartsCast;
col++;
}
}
}
but i don't see where you define variables row, col and StartDate.
You could query all in one go by doing .Where(p => p.DATE_TIME >= StartDate && p.DATE_TIME <= END_DATE).GroupBy(p => p.DATE_TIME.Hour)

Return date records to nearest minute using linq

How do I return a list of dates to the nearest minute. I need the linq query to return only dates the match the passed date to the nearest 3 minutes
return EntitySet.Count(f => f.VISITDATE == dt.Date);
I use the code like this usually:
if((date2 - date1).Minutes <= 3) {
Console.WriteLine("Do not add again!!");
}
var dates = (from ES in EntitySet
where ES.VISITDATE >= dt.Date.AddMinutes(-3) && ES.VISITDATE <= dt.Date.AddMinutes(3)
select ES.VISITDATE)
var result = EntitySet.Where(item => Math.Abs((item.VISITDATE - dt.Date).TotalMinutes) <= 3);

Categories

Resources