I try to bring totals according to the days, but sometimes the days can be elongated. For example, a time period of one day is between 00:00 and 23:59. When you make the following code according to this timeframe, it doesn't matter, it gives the correct totals.
opening = opening.AddDays(-6);
var totals = await _unitOfWork.Additions.GetAll().Where(x => x.FirmId == firm.FirmId && x.State == false && x.Closing >= opening && x.Closing <= closing)
.GroupBy(x => new { x.Closing.Value.Year, x.Closing.Value.Month, x.Closing.Value.Day })
.Select(s => new
{
onKey = Convert.ToDateTime(new DateTime(s.Key.Year, s.Key.Month, s.Key.Day)).ToShortDateString(),
total = s.Sum(c => c.Price)
}).ToListAsync();
However, opening and closing hours may vary according to the enterprises, so the day totals do not give the correct results for each enterprise.
For example, if a business's opening time is between 07:00 in the morning and 02:00 in the night, the above block of code will not work correctly. Because, assuming the last closed account is 01:00 at night, the code block above assigns this closed account to another day because the closed account is closed on the new day.
But what I want is that I need the sum of a business between morning opening and night closing time.
For example, --->
Company 1 opening : 07:00 A.M closing : 23:59 PM time range [07:00-23:59] output --> *18.11.2019 total: 1000$
Company 2 opening : 07:00 A.M closing : 02:00 AM (next day 19.11.2019 ) time range [07:00-02:00] output --> 18.11.2019 total: 1200$
I tried this solution but I got the 'must be reducible node' error.
try
{
TimeSpan start = new TimeSpan(Convert.ToInt32(firm.OpeningHours.Split('-')[0].Split(':')[0]), Convert.ToInt32(firm.OpeningHours.Split('-')[0].Split(':')[1]), 0);
var totals = await _unitOfWork.Additions.GetAll().Where(x => x.FirmId == firm.FirmId && x.State == false && x.Closing >= opening && x.Closing <= closing)
.GroupBy(x =>
new {
Y = x.Closing.Value.Year,
M = x.Closing.Value.Month,
D = x.Closing.Value.TimeOfDay >= start ? x.Closing.Value.Day : x.Closing.Value.Day - 1
})
.Select(s => new
{
onKey = Convert.ToDateTime(new DateTime(s.Key.Y, s.Key.M, s.Key.D)).ToShortDateString(),
total = s.Sum(c => c.Price)
}).ToListAsync();
return new BaseResponse<object>(totals);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
return new BaseResponse<object>("null");
}
DB provider which I am using is Pomelo.EntityFrameworkCore.MySql
You should be able to get accurate results if you adjust your group by expression. The effective closing date for each addition depends on the firm opening hours. Something like this should work:
var totals = await _unitOfWork
.Additions
.GetAll()
.Where(x =>
x.FirmId == firm.FirmId &&
x.State == false &&
x.Closing >= opening &&
x.Closing <= closing)
.GroupBy(x =>
new {
x.Closing.Value.Year,
x.Closing.Value.Month,
(x.Closing.Value.TimeOfDay >= firm.OpeningHours)? x.Closing.Value.Day : x.Closing.Value.Day - 1
})
.Select(s => new
{
onKey = Convert.ToDateTime(new DateTime(s.Key.Year, s.Key.Month, s.Key.Day)).ToShortDateString(),
total = s.Sum(c => c.Price)
}).ToListAsync();
Related
I am trying to create a pop up to warn a user they must update a part in inventory if the part date is more than 90 days old. The date on the sheet (Cell Q5) is autofilled from another sheet, but that shouldn't matter. The value for the cell on the spreadsheet is 9/2/2021. I've tried many things, but currently I am getting the value for Q5 showing up as NaN .
function CheckInvDate() {
var ss = SpreadsheetApp.getActive().getId();
var partsrange = Sheets.Spreadsheets.Values.get(ss, "BOM!A5:Q5");
var currentDate = new Date();
var parthist = new Date();
parthist.setDate(currentDate.getDate() -90);
for (var i = 0; i < partsrange.values.length; i++){
var name = partsrange.values [i][1]
var partdate = partsrange.values [i][16]
var parthisttime = new Date(parthist).getTime();
var partdatetime = new Date(partdate).getTime();
Logger.log("History " + parthisttime)
Logger.log("Current " + partdatetime)
SpreadsheetApp.flush()
// if (parthist > partdate == "TRUE") {
// SpreadsheetApp.getUi().alert('The price on '+ name + ' is out of date. Please update price and try again.')
// }
}
}
My last log was
[22-07-06 11:50:55:851 EDT] History 1649346655850
[22-07-06 11:50:55:853 EDT] Current NaN
I've seen a number of responses on Stack Overflow, but I can't understand them. They seem to refer to variables that I don't see in code, or commands I haven't seen before and I'm not sure if they are in date.
Try this:
function CheckInvDate() {
const ss = SpreadsheetApp.getActive();
const vs = Sheets.Spreadsheets.Values.get(ss.getId(), "BOM!A5:Q5").values;
let d = new Date();
d.setDate(d.getDate() - 90)
const dv = d.valueOf();
const oldthan5 = vs.map(r => {
if (new Date(r[16]).valueOf() < dv) {
return r;
}
}).filter(e => e);
SpreadsheetApp.getUi().showModelessDialog(HtmlService.createHtmlOutput(`<textarea rows="12" cols="100">${JSON.stringify(oldthan5)}</textarea>`).setWidth(1000), "Older Than 90 Days");
}
This outputs a dialog with the rows older than 90 days
I went to try this on my script again after lunch, and for whatever reason I am no longer getting the NaN value. I made one change on the if statement to fix the logic, and now it is working correctly. Not sure what I did, but apparently the coding gods were unhappy with me before.
The only part I changed was
if (parthisttime > partdatetime) {
SpreadsheetApp.getUi().alert('The price on '+ name + ' is out of date. Please update price and try again.')
}
I have a data layer using Entity Framework 6 Database First. One of my entities represents a time span - it has a start date and an end date.
public class Range
{
public Guid ID { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
}
I want to add validation that ensures Ranges never have overlapping dates. The application will be saving added, modified and deleted Ranges all at once. So I was playing with overriding ValidateEntity in my DbContext. Here is what I ended up with and then realized I still have a problem:
protected override DbEntityValidationResult ValidateEntity(DbEntityEntry entityEntry, IDictionary<object, object> items)
{
var result = new DbEntityValidationResult(entityEntry, new List<DbValidationError>());
if (entityEntry.Entity is Range && (entityEntry.State == EntityState.Added || entityEntry.State == EntityState.Modified))
{
Range range = entityEntry.Entity as Range;
if (Ranges.Local.Any(p => range.EndDate >= p.StartDate && range.StartDate <= p.EndDate) ||
Ranges.Any(p => range.EndDate >= p.StartDate && range.StartDate <= p.EndDate))
{
result.ValidationErrors.Add(
new System.Data.Entity.Validation.DbValidationError("EndDate", "Cannot overlap another range.")); //Could be StartDate's fault but we just need save to fail
}
}
if (result.ValidationErrors.Count > 0)
{
return result;
}
else
{
return base.ValidateEntity(entityEntry, items);
}
}
The problem is that Ranges.Local does not query the database, and Ranges, which queries the database, does not include the unsaved changes.
For example: If I have a Range starting 9/1 and ending 9/8 in the database, which I modified in memory to start 8/1 and end 8/8, and I have also added a new Range starting 9/6 ending 9/12, then Ranges.Any(p => blockOut.EndDate >= p.StartDate && blockOut.StartDate <= p.EndDate) will return true for the added Range because the currently saved Range overlaps. But it is actually valid because in memory, those dates have been changed and when it saves there will be no overlaps.
Is there any way to query the combination of Local and the database, i.e. only query the database for records that are not in Local?
EDIT:
Updated code that I think works in response to Steve Py's answer below:
//Check Local first because we may be able to invalidate without querying the DB
if (BlockOutPeriods.Local.Any(p => blockOut.ID != p.ID && blockOut.EndDate >= p.StartDate && blockOut.StartDate <= p.EndDate))
{
result.ValidationErrors.Add(
new System.Data.Entity.Validation.DbValidationError("EndDate",
"Block out cannot overlap another block out."));
}
else
{
var editedIDs = BlockOutPeriods.Local.Select(p => p.ID);
//!Contains avoids reading & mapping all records to Range model objects - better?
if (BlockOutPeriods.Any(p => blockOut.EndDate >= p.StartDate && blockOut.StartDate <= p.EndDate && !editedIDs.Contains(p.ID)))
{
result.ValidationErrors.Add(
new System.Data.Entity.Validation.DbValidationError("EndDate",
"Block out cannot overlap another block out."));
}
}
You could use a Union between the local state and data state, providing an IEqualityComparer to match the PKs so that local is used without duplicating from data set.
var result = Ranges.Local
.Where(p => range.EndDate >= p.StartDate || range.StartDate <= p.EndDate)
.Union(
Ranges.Where(p => range.EndDate >= p.StartDate || range.StartDate <= p.EndDate),
new RangeEqualityComparer())
.Any(p => p.RangeId != range.RangeId && range.EndDate >= p.StartDate && range.StartDate <= p.EndDate);
This should:
pull any ranges from Local which potentially span the desired start/end date.
union with any ranges from DB which potentially span the desired date range.
check for the existence of any other record (<> my ID) that covers the range.
I have created an Apps Script to compile data and save the results to a new Google Sheet.
The code gets the current date with new Date() and uses that for the query and to name the new sheet it creates.
Here is the relevant part of the code:
function exportPayroll(setDate){
var date = new Date();
var newWeek = Utilities.formatDate(_getSunday(date),"GMT", "MM/dd/yyyy");
for (var company in companies){
getPayroll(companies[company],newWeek);
}
}
function _getSunday(d) {
d = new Date(d);
var day = d.getDay(),
diff = d.getDate() - day + (day == 0 ? -7:0); // adjust when day is sunday
return new Date(d.setDate(diff));
}
If I run exportPayroll manually, I get the exact results that I expect. So, I setup this trigger to automate the process:
When the trigger runs, the date value is 12/31/1969 instead of today.
Why does it act different with the trigger? Checking the execution transcript, I don't see any error messages.
Is there a better way to get today's date via a trigger?
Since you just wan't to get the Date of the previous sunday. Try running it this way.
function getLastSunday() {
var d = new Date();
var day = d.getDay();
var diff = d.getDate() - day + (day == 0 ? -7:0); // adjust when day is sunday
return new Date(d.setDate(diff));
}
function exportPayroll(){
var newWeek = Utilities.formatDate(getLastSunday(),"GMT", "MM/dd/yyyy");
for (var company in companies){
getPayroll(companies[company],newWeek);
}
}
I am trying to write the EF Query with a filter and the generated SQL is not using WHERE statement on the SQL Server. It extracts all data and does the filter on the Client's Memory. I am quite worried about the performance and would like to apply the filter on the SQL Server.
The requirement is to count the number of records in an Hour time slot.
public async Task<int> GetNumberofSchedules(DateTime dt)
{
return await _context.Schedules.CountAsync(
s => s.state == 0
&& s.AppointmentDate.Value.Date == dt.Date
&& s.AppointmentTime.Value.Hours >= dt.Hour
&& s.AppointmentTime.Value.Hours < dt.AddHours(1).Hour
);
}
Sample Data
if given DateTime is 07/04/2017 08:20, it should return the number of records between 07/04/2017 08:00 and 07/04/2017 09:00
The count does return 6 which is correct but when I trace the generated SQL in SQL Profiler, it's retrieving all data.
Generated SQL in SQL Profiler
SELECT
[s].[EnrolmentRecordID], [s].[AF1], [s].[AcademicYearID], [s].[AdminName], [s].[AppForm], [s].[AppointmentDate],
[s].[AppointmentTime], [s].[BDT], [s].[BKSB], [s].[DateCreated],
[s].[DateModified], [s].[Employer], [s].[EmployerInvited],
[s].[EmployerReps], [s].[MIAPP], [s].[NI], [s].[ProposedQual],
[s].[SMT], [s].[StudentInvited], [s].[StudentName]
FROM
[dbo].[EN_Schedules] AS [s]
I would like to amend my EF code to generate WHERE statement and do the filter on the server side. How can I achieve it?
Update1:
If I remove filters for TimeSpan value, it generates the correct SQL statement as the following: So, it seems to me that I need to apply the filter differently for TimeSpan Field.
exec sp_executesql N'SELECT COUNT(*) FROM [dbo].[EN_Schedules] AS [s]
WHERE ([s].[state] = 0) AND (CONVERT(date, [s].[AppointmentDate]) =
#__dt_Date_0)',N'#__dt_Date_0 datetime2(7)',#__dt_Date_0='2017-04-07
00:00:00'
**Update2: **
By using Ivan's solution, I ended up doing like this:
var startTime = new TimeSpan(dt.Hour, 0, 0);
var endTime = new TimeSpan(dt.Hour + 1, 0, 0);
return await _context.Schedules.CountAsync(
s => s.state == 0
&& s.AppointmentDate.Value.Date == dt.Date
&& s.AppointmentTime.Value >= startTime
&& s.AppointmentTime.Value < endTime
);
It's indeed converted to client evaluation - looks like many TimeSpan operations are still not supported well by EF Core.
After a lot of trial and error, the only way currently you can make it translate to SQL is to prepare TimeSpan limits as variables and use them inside the query:
var startTime = new TimeSpan(dt.Hour, 0, 0);
var endTime = startTime + new TimeSpan(1, 0, 0);
return await _context.Schedules.CountAsync(
s => s.state == 0
&& s.AppointmentDate.Value.Date == dt.Date
&& s.AppointmentTime.Value >= startTime
&& s.AppointmentTime.Value < endTime
);
It looks like Client Evaluation is the reason.
Disable it with ConfigureWarnings call. It will give an exception if a LINQ statement cann't be translated to SQL:
public class FooContext : DbContext
{
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder
.UseSqlServer("foo_connstr")
.ConfigureWarnings(warnings => warnings.Throw(RelationalEventId.QueryClientEvaluationWarning));
}
}
I have a entity called StockDetails using Entity Framework, see picture below
I want to fetch a list IEnumerable<StockDetail>, summarized by Reels, Qtyton, average date from Days (datetime) and grouping by the rest of the properties.
I'm building a datalayer (WCF Services) with Entity Framework as ORM, some of the services are old SQL queries I'm trying to convert to linq/lamdba expression. But I'm pretty new to how to write and want some help.
This is how I started the query in lambda, but I got stuck on the groupby/sum/average part.
public IEnumerable<StockDetail> ListStockDetailByCustomerNumber(int customerNumber)
{
var CustNo = customerNumber.ToString();
return _entities.StockDetails
.Where(x => x.Custno == CustNo)
.GroupBy(
x =>
new
{
x.Millcd,
x.Matercd,
x.Proddesc,
x.Grammage,
x.Reelwidth,
x.Ordercode,
x.Buyordno,
x.Whsedesc,
x.Co,
x.Finished,
x.Pm,
x.PurchaseOrder,
x.Diameter,
x.Rtadate,
x.Custno,
x.Reels,
x.Days,
x.Qtyton
})
.ToList();
}
Question solved:
public IEnumerable<StockDetail> ListStockDetailByCustomerNumber(int customerNumber)
{
var stockDetailsList = new List<StockDetail>();
var custNo = customerNumber.ToString();
var list = _entities.StockDetails
.Where(x => x.Custno == custNo )
.GroupBy(
x =>
new
{
x.Millcd,
x.Matercd,
x.Proddesc,
x.Grammage,
x.Reelwidth,
x.Ordercode,
x.Buyordno,
x.Whsedesc,
x.Co,
x.Finished,
x.Pm,
x.PurchaseOrder,
x.Diameter,
x.Rtadate,
x.Custno,
x.UpdDte
})
.Select(x => new
{
x.Key.Millcd,
x.Key.Matercd,
x.Key.Proddesc,
x.Key.Grammage,
x.Key.Reelwidth,
x.Key.Ordercode,
x.Key.Buyordno,
Reels = x.Sum(p => p.Reels),
Qtyton = x.Sum(p => p.Qtyton),
Day = x.Max(p => p.Days),
//Day = x.Average(p => p.Days.Ticks), // Want to calculate average datetime of date but linq dosn't support datetime.ticks
x.Key.Whsedesc,
x.Key.Co,
x.Key.Finished,
x.Key.Pm,
x.Key.PurchaseOrder,
x.Key.Diameter,
x.Key.Rtadate,
x.Key.Custno,
x.Key.UpdDte
});
foreach (var s in list)
{
stockDetailsList.Add(new StockDetail
{
Millcd = GetFriendlyNameForKey(s.Millcd),
Matercd = s.Matercd,
Proddesc = s.Proddesc,
Grammage = s.Grammage,
Reelwidth = s.Reelwidth,
Ordercode = s.Ordercode,
Buyordno = s.Buyordno,
Reels = s.Reels,
Qtyton = s.Qtyton,
Days = s.Day,
Whsedesc = s.Whsedesc,
Co = s.Co,
Finished = s.Finished,
Pm = s.Pm,
PurchaseOrder = s.PurchaseOrder,
Diameter = s.Diameter,
Rtadate = s.Rtadate,
Custno = s.Custno,
UpdDte = s.UpdDte
});
}
return stockDetailsList;
}
This is how the query looks in T-SQL
SELECT
Millcd, Matercd,
Proddesc, Grammage,
Reelwidth, Ordercode,
Buyordno,
SUM(Reels) as Reels,
SUM(Qtyton) as Qtyton,
Whsedesc, Co,
(cast(FLOOR(avg(cast(DateProd as float))) as datetime)) As Days,
Finished, Pm,
PurchaseOrder,
Diameter, Rtadate,
Custno, UpdDte
FROM StockDetail
WHERE custno = #custcode
GROUP BY Millcd, Matercd, Proddesc, Grammage, Reelwidth, Ordercode, Buyordno,
Whsedesc, Co, Finished, Pm, PurchaseOrder, Diameter, Rtadate, Custno, UpdDte
not sure if this will help you but you can add
Reels = (_entities.StockDetails
.Where(x => x.Custno == CustNo).Sum(x=>x.Reels))
instead of x.Reels in your select , and do the same with Qtyton
For your average use the average extension
your select will look something like .Select(x=>new {...}) after your where statement then the group by