LINQ Sum of prices in each month in loop? - c#

So I'm trying to have a list of profits in each month from my database.
I want to do this in loop, I thnik it will be the best soulution.
Here we have a loop, that I want to count a profit for each month.
using (var context = new ModelContext("ConnectionStringDbMagazynier"))
{
for (int i = 0; i < 12; i++)
{
decimal q = (from ar in context.Archives
where ar.SalesDate <= DateTime.Now.AddYears(-1).AddMonths(i + 1) && ar.SalesDate >= DateTime.Now.AddYears(-1).AddMonths(i)
let sum = context.Archiwum.Sum(x => x.Price)
select sum);
profits[i] = decimal.ToDouble(q);
}
}
from this query i get an error:
Error 2 Cannot implicitly convert type 'System.Linq.IQueryable<decimal>' to 'decimal'
My question is, how to make it witohut error? and is this solution ok? What in case, that i didn't sell anything in partiuclar month and the sum is null?

It's easier to use lambda syntax in this case.
var q = context.Archives
.Where(ar => ar.SalesDate <= DateTime.Now.AddYears(-1).AddMonths(i + 1) && ar.SalesDate >= DateTime.Now.AddYears(-1).AddMonths(i))
.Sum(x => x.Price);
Or if you really like the query syntax
var records = (from ar in context.Archives
where ar.SalesDate <= DateTime.Now.AddYears(-1).AddMonths(i + 1) && ar.SalesDate >= DateTime.Now.AddYears(-1).AddMonths(i)
select ar);
profits[i] = records.Sum(x => x.Price);

var q = context.Archives
.Where(p => p.SalesDate <= DateTime.Now.AddYears(-1).AddMonths(i + 1) && p.SalesDate >= DateTime.Now.AddYears(-1).AddMonths(i))
.Sum(x => x.Price.HasValue ? x.Price.Value : 0);
It's possible get month stat without loop
var grouped = context.Archives.Where(p=> p.SalesDate <= DateTime.Now.AddYear(-1))
.GroupBy(k => new {Year = k.Year, Month = k.Month})
.Select(p=> {Date = p.Key, Sum = p.Sum(x=> x.Price.HasValue ? x.Price.Value : 0)})

Related

where clause not working in group by LINQ c sharp

I have a table "register_operation with fields"
[Key]
int id_registru_casa ,
DateTime data ,
int id_cont_sintetic ,
decimal suma ,
string tip
tip can take only 2 value :"receipts" and "payments"
"Groupby" work with no problem
but when I add "where" clause not working
(it doesn't show me any records)
(although there are recordings in database with day 19, month 9 and tip=receipts)
var centralizator_rc = db.register_operation
.Where(i => (i.data.Day == 19) && (i.data.Month == 9) && (tip=="receipts"))
.GroupBy(i => i.id_cont_sintetic)
.Select(g => new {
id_cont_sintetic = g.Key,
total_receipts = g.Sum(i=>i.suma),
}).ToList();
Thanks!
SOLVED!
I change code like this:
var centralizator_rc = db.registru_casa
.Where(crc=>(crc.data.Month==8) && (crc.data.Day==16) && (crc.tip=="receipts"))
.GroupBy(crc=> new
{
crc.id_cont_sintetic,
crc.data.Month,
crc.data.Day,
crc.tip
})
.Select(g => new {
data = ziuaOK,
id_cont_sintetic = g.Key.id_cont_sintetic,
total_incasare = g.Sum(i => i.suma),
}).ToList();

Linq to SQL C#: items between dates

I have 2 tables in my SQL server 2012:
Errors (id, cityID, centerID, date)
InspectionVisits (id, cityID, centerID, datePerformed)
I am trying to get the number of errors between inspection visits to see if there is an improvement in the center with the specific centerID and build a chart.
This is my code so far but I can't find out how I can write the where clause to get the number of errors between these inspection visits:
var errorsPerIV = from e in dc.Errors
where e.cityID == ctid && e.centerID == centerid
group e by e.date.Date into g
join iv in dc.InspectionVisits on g.FirstOrDefault().cityID equals iv.cityID
where iv.centerID == g.FirstOrDefault().centerID
select new
{
Day = g.Key.Day + "/" +
g.Key.Month + "/" +
g.Key.Year,
Errors = g.Count()
};
Sample Case
Something like: 5 errors between Inspection_Visit_1 and Inspection_Visit_2, 2 errors between Inspection_Visit_2 and Inspection_Visit_3 and 1 error between Inspection_Visit_3 and today.
EDIT
Maybe it could work if I show queries per day and mark only the inspection visits in the chart's x axis.
I'm not sure if there is a better way, but you could do something like the following
Suppose you have a class like
public class Summary
{
public DateTime? PreviousInspection;
public DateTime? NextInspection;
public int Errors;
}
then you can get most of the information by having a query like
var errorsPerIV = (from e in dc.Errors
where e.cityID == ctid && e.centreID == centreid
// Find the date of the previous inspection (if any)
let previousInspection = (from i in dc.InspectionVisits where i.cityID == e.cityID && i.centreID == e.centreID && i.datePerformed <= e.date orderby i.datePerformed descending select i.datePerformed).FirstOrDefault()
// Find the date of the next inspection (if any)
let nextInspection = (from i in dc.InspectionVisits where i.cityID == e.cityID && i.centreID == e.centreID && i.datePerformed > e.date orderby i.datePerformed ascending select i.datePerformed).FirstOrDefault()
group e by new { previousInspection , nextInspection } into results
orderby results.Key.previousInspection
select new Summary
{
PreviousInspection = results.Key.previousInspection,
NextInspection = results.Key.nextInspection ,
Errors = results.Count()
})
.ToList();
However if there are no errors between two visits, then these visits will not appear in your list, so you need to find all the visits and see if there missing, ie something like
var inspectionsDates = (from i in InspectionVisits where i.cityID == ctid && i.centreID == centreid orderby i.datePerformed select i.datePerformed).ToList();
for(int i=0; i< inspectionsDates.Count-1; i++)
{
if (!errorsPerIV.Any(a=>a.PreviousInspection == inspectionsDates[i]))
{
errorsPerIV.Add(new Summary() { PreviousInspection = inspectionsDates[i], NextInspection = inspectionsDates[i + 1], Errors = 0});
}
}
I think this would be easiest processed on the client side.
First you want to get the interesting InspectionVisits and order them by date, then convert to an Enumerable to pull them to the client:
var orderedIVs = InspectionVisits.Where(iv => iv.cityID == ctid && iv.centerID == centerid).Select(iv => iv.dateperformed).OrderBy(ivdp => ivdp).AsEnumerable();
Now using an extension method that processes along the Enumerable to compute a running value (it is called Scan because it is modeled after the APL Scan operator, which is like an Aggregate that returns all the intermediate values):
// TKey combineFn(T prev, T cur)
// TKey lastKeyFn(T cur)
public static IEnumerable<TResult> Scan<T, TResult>(this IEnumerable<T> src, Func<T, T, TResult> combineFn, Func<T, TResult> lastKeyFn) {
using (var srce = src.GetEnumerator()) {
if (srce.MoveNext()) {
var prev = srce.Current;
while (srce.MoveNext()) {
yield return combineFn(prev, srce.Current);
prev = srce.Current;
}
yield return lastKeyFn(prev);
}
}
}
You can compute the periods for the inspection visits:
var IVPeriods = orderedIVs.Scan((prev, cur) => new { Begin = prev, End = cur }, cur => new { Begin = cur, End = DateTime.Now });
Finally, with the periods you can count the Errors that occurred between each period:
var errorsPerIV = IVPeriods.Select(ivp => new { Day = ivp.Begin.Date, Count = Errors.Where(e => ivp.Begin <= e.date && e.date <= ivp.End).Count() });
If you want to process this on the server side, you must join the InspectionVisits table to itself so you can create the periods. I have not tested this with SQL server:
var orderedIVs = InspectionVisits.Where(iv => iv.cityID == ctid && iv.centerID == centerid).Select(iv => iv.dateperformed).OrderBy(ivdp => ivdp);
var IVPeriods = (from ivb in orderedIVs
from ive in orderedIVs
where ivb < ive
group ive by ivb into iveg
select new { Begin = iveg.Key, End = iveg.OrderBy(iv => iv).First() })
.Concat((from ivb in orderedIVs orderby ivb descending select new { Begin = ivb, End = DateTime.Now }).Take(1));
var errorsPerIV = IVPeriods.Select(ivp => new { Day = ivp.Begin.Date, Count = Errors.Where(e => ivp.Begin <= e.date && e.date <= ivp.End).Count() });

How to pass 2 dates in .Where?

currently in the code I am outputting the employees table which have end dates of anything from before today's date and up to 90 days. I am trying to make it so only employees with maximum -30 days and + 90 of today's date.
(Fairly new so be easy)
(NOT SURE HOW TO CORRECTLY USE 2 DATES FOR .ADDDAYS )
Thanks
public ActionResult Index()
{
var employees = db.employees;
var today = DateTime.Today.AddDays(90);
var past = DateTime.Today.AddDays(-30);
var q = db.employees.Where(t => t.EndDate <= today );
return View(q.OrderByDescending(t => t.EndDate));
}
Why can't you use a AND (&&) condition like
var q = db.employees.Where(t => t.EndDate <= today && t.EndDate >= past);
It is simple by using conditional-AND && operator & combining both queries as single line:
var q = db.employees.Where(t => t.EndDate >= past && t.EndDate <= today)
.OrderByDescending(t => t.EndDate);
return View(q);
Try following code. You can add the OrderByDescending(t => t.EndDate) on the first line of code.
var q = db.employees.Where(t => t.EndDate >= past && t.EndDate<= today)OrderByDescending(t => t.EndDate);
return view(q);

Count users where subscription end within month Linq c#

I just want to count users where their subscription end date is within the coming month:
int Count = oUsers
.Where(x =>
0 < x.SubscriptionEnddate.Value.Subtract(DateTime.Now).Days < 30)
.Count();
But its not working.
What I want to do is 0 < Value < 30.
Use &&. Period. It is where it is designed for.
You can circumvent this by creating a Between extension method, or concatenate two Where clauses, but really, why trade that over &&. But if you insist:
int c = oUsers
.Select(x=> x.SubscriptionEnddate.Value.Subtract(DateTime.Now.AddDays(30)).Days)
.Where(d => 0 < d)
.Where(d => d < 30)
.Count();
you can try something like this
int count = oUsers.Where(x=> x.Days > 0).Count(x => x.Days < 30);
I would do it this way to keep it readable:
int Count =
oUsers
.Select(u => new
{
u,
days = u.SubscriptionEnddate.Value.Subtract(DateTime.Now.AddDays(30)).Days,
})
.Where(x => x.days > 0 && x.days < 30)
.Select(x => x.u)
.Count();
This obviously uses && but it eliminates the code duplication which I think is what you're really trying to avoid.
The use of .Count() at the end removes the need to keep track of the original value, so, as Patrick has already posted, this suffices:
int Count =
oUsers
.Select(u => u.SubscriptionEnddate.Value.Subtract(DateTime.Now.AddDays(30)).Days)
.Where(x => x > 0 && x < 30)
.Count();
Not always the shorter why is better.
When someone else will read your code it will be much easier for him to understand when you calling SomeRange method
I think the best why is when the code more readable so you can do method for returning if your value is match the start and end ranges and call it.
example for some int range but the same why you can check dates
public static bool SomeRange(int value,int start,int end)
{
return (value > start && value < end);
}
static void Main()
{
List<int> valueList = new List<int> { 1,65,3,76,34,23,11,5,665,334};
var result = valueList.Where(x => SomeRange(x, 0, 30)).Count();
}
Yes Solve it with Enumerable.Range:
oUsers.Where(x => x.SubscriptionEnddate != null && Enumerable.Range(0, 30).Contains(x.SubscriptionEnddate.Value.Subtract(DateTime.Now).Days)).Count();
I'd say, precompute the range start and end before executing the count, then compare for start && end:
// some test data
var oUsers = Enumerable.Range(0, 100).Select(x => new { SubscriptionEnddate = (DateTime?)DateTime.Now.AddDays(x) });
var rangeStart = DateTime.Now;
var rangeEnd = rangeStart.AddDays(30);
// Conditional count... can also be done as .Where(condition).Count()
int Count = oUsers.Count(x => x.SubscriptionEnddate >= rangeStart && x.SubscriptionEnddate < rangeEnd);
Possibly use DateTime.Today instead since you are only interested in days.
You could use nested if statements, if you're really opposed to using &&
if x.SubscriptionEnddate.Value.Subtract(DateTime.Now.AddDays(30)).Days > 0 {
if x.SubscriptionEnddate.Value.Subtract(DateTime.Now.AddDays(30)).Days < 30 {
int count = oUsers;
}
}
This seems like an incredibly obtuse way to go about this though. This is why we have the && operator.

Linq Min in Select new & Multiple GroupBy columns

I want to write following query in Linq
INSERT INTO INOUTNEW (CODE,INDATE,TIME_DATE1,INOUTFLAG,TIME_FLD1,TIME_FLD2,TIME_FLD3)
SELECT CODE,MIN(INDATE),TIME_DATE1,'I',TIME_FLD1,TIME_FLD2,'31/05/2015' FROM INOUT
WHERE TIME_FLD1='T0003' AND INDATE >= '31/05/2015' AND INDATE <= '31/05/2015'
AND TIME_DATE1='31/05/2015'
GROUP BY CODE,TIME_DATE1,TIME_FLD1,TIME_FLD2
SO I am trying this :-
var data = ctx.tblInOut.Where(m => m.CompanyId == companyId && m.Time_Field1 == item.ShiftCode && m.InDate == StrInStart && m.InDate <= StrInEnd && m.Time_Date1 == InputDate).Select(m =>
new
{
EmployeeId = m.EmployeeId,
InDate = Min(m.InDate),
Time_Date1 = m.Time_Date1,
InOutFlag = m.InOutFlag
}).ToList();
I am stuck in Min Part. How to get Min in Select? And How to add multiple GroupBy in Linq?
Try something like this:
var data = ctx.tblInOut
.Where(m =>
m.CompanyId == companyId &&
m.Time_Field1 == item.ShiftCode &&
m.InDate == StrInStart &&
m.InDate <= StrInEnd &&
m.Time_Date1 == InputDate
)
.GroupBy(m =>
new {
m.Code,
m.Time_Date1,
m.Time_FLD1,
m.Time_FLD2
})
.Select(g =>
new
{
m.Key.Code,
InDate = m.Min(gg => gg.InDate),
m.Key.Time_Date1,
Something = "I",
m.Key.Time_FLD1,
m.Key.Time_FLD2,
SomeDate = "31/05/2015"
}).ToList();
To get Min, you must group first - otherwise it's trying to call Min on a single element.
.Key simply references the key of the group (in this case, a tuple of Code, Date1, Time_FLD1, Time_FLD2)

Categories