public class Results
{
public DateTime Date {get; set;}
public decimal Result {get; set;}
}
public class Sums
{
public decimal YearlySum {get; set;}
public decimal MonthlySum {get; set;}
public DateTime Date {get; set;}
}
I have a collection of Results object.
I want to populate Sums list with Yearly, Monthly sums based on date.
YearlySum is the sum of all Results' values in the provided date's year (until the provided date), and MonthlySum is the sum of Results' values in the provided date's month (until the provided date)
How to do it using Linq?
Something like this function should do the work :
public Sum GetSumFromResultsAndDate(IEnumerable<Results> results, DateTime date) {
return new Sum {
Date = date,
MonthlySum = results
.Where(r => r.Date.Year == date.Year && r.Date.Month == date.Month && r.Date <= date)
.Sum(r => r.Value) ,
YearlySum = results
.Where(r => r.Date.Year == date.Year && r.Date <= date)
.Sum(r => r.Value)
}
}
(make the sum of all Result which have the same month and year than provided date for monthly sum, and sum of all results which have the same year as the provided date for the yearly sum)
EDIT : added the "result date inferior to provided date" condition as per question clarification
Related
I'm having a list of budget units each one containing the following properties:
DateTime Month,
int IdCurrency,
decimal Planned,
int sign, //denotes whether we have income (1) or cost (0)
etc...
Based on given year, I'd like to return a list of objects of the following structure:
public class BudgetBalances
{
public DateTime Month { get; set; }
public int IdCurrency { get; set; }
public decimal Incomes { get; set; }
public decimal Costs { get; set; }
public decimal Balance { get; set; }
}
The first part is easy - I'm getting all budget units for given day from the database, but now I do not know how to make an EF query to:
Get all incomes (sign==1) in currencies within one month, sum them and store it Incomes property
Get all costs (sign==0) and do the same as above
Substract Cost from Income and store it under Balance property
As the result I will have
Jan2022, USD, 3000, 1000, 2000
Jan2022, EUR, 5000, 2000, 3000
etc..
I can always make three level foreach structure, but that is not an effective way to do so. Could you please give me hint how to do it proper way?
That is what I got so far:
public List<BudgetBalances>GetYearlyBudget(int IdOwner, int year)
{
var budgets = _context.Budgets
.Where(_ => _.Month.Year == year && _.IdOwner == IdOwner);
List<BudgetBalances> list = budgets.GroupBy(a => a.Month)
.Select(ls => new BudgetBalances
{
Incomes = ls.Where(_ => _.IsIncome == 1).Sum(_ => _.Planned),
Costs = ls.Where(_ => _.IsIncome == 0).Sum(_ => _.Planned)
}).ToList();
return list;
}
And it calculates each month budget taking into account incomes and costs, but it does not take currencies into consideration. Also I do not know how should I obtain balance value.
Balance = Income - Costs
does not work
Reference this
code sample
using (var context = new MyContext())
{
var result = context.BudgetBalances
.Where(b => b.IdCurrency == 1);
}
Thanks, finally I got what I wanted, here's my code:
public List<BudgetBalances>GetYearlyBudget(int IdOwner, int year)
{
var budgets = _context.Budgets
.Where(_ => _.Month.Year == year && _.IdOwner == IdOwner);
List<BudgetBalances> list = budgets.GroupBy(a => new { a.Month, a.IdCurrency})
.Select(ls => new BudgetBalances
{
IdCurrency = ls.Key.IdCurrency,
CurrencySymbol = _context.Currencies.Where(_=>_.IdCurrency==ls.Key.IdCurrency).FirstOrDefault().CurrencySymbol,
Month = ls.Key.Month,
Incomes = ls.Where(_ => _.IsIncome == 1).Sum(_ => _.Planned),
Costs = ls.Where(_ => _.IsIncome == 0).Sum(_ => _.Planned),
})
.OrderBy(_=>_.Month)
.ToList();
foreach(BudgetBalances ls in list)
{
ls.Balance = ls.Incomes - ls.Costs;
ls.month = ls.Month.ToString("MM/yyyy");
}
return list;
}
I want List (containing HoursCount and date) of record from database for specifies date-range
if for some date, records are not available in database then that date should be in list with count = 0
// Where GetDateWistUsage is...
public List<GraphData> GetDateWiseUsage(DateTime startDate, DateTime endDate)
{
return Set.Where(x => x.Date >= startDate.Date && x.Date < endDate.Date)
.GroupBy(x => x.Date)
.Select(x => new GraphData { Date = x.Key.Date, TotalHoursCount = x.Sum(i => i.TotalHours }).ToList();
}
// Where GraphData is
public class GraphData
{
public DateTime Date { get; set; }
public double TotalHoursCount { get; set; }
}
Here, suppose if we are passing Date 1-Dec-2018 to 20-Dec-2018 and for 15-Dec-2018 no record present in database. In that case, List should also contain 15-Dec-2018 with TotalHoursCount = 0
You should create some dummy data to fill in the gaps. Since you're doing a Sum, you can just create some dummies of your input data.
To do so, I've assumed that Set is List<SourceData> - change this as necessary:
public class SourceData
{
public DateTime Date { get; set; }
public long TotalHours { get; set; }
}
public class GraphData
{
public DateTime Date { get; set; }
public long TotalHoursCount { get; set; }
}
I've then added a method to get a date range (upper bound exclusive):
public static IEnumerable<DateTime> GetDateRange(DateTime startDate, DateTime endDate)
{
return Enumerable.Range(0, (endDate - startDate).Days).Select(d => startDate.AddDays(d));
}
Finally, I've updated your GetDataWiseUsage method to generate the date range and convert it into dummy source data:
public static List<GraphData> GetDateWiseUsage(DateTime startDate, DateTime endDate)
{
return Set.Where(x => x.Date >= startDate.Date && x.Date < endDate.Date)
.Concat(GetDateRange(startDate, endDate).Select(date => new SourceData() { Date = date, TotalHours = 0 }))
.GroupBy(x => x.Date.Date)
.Select(x => new GraphData { Date = x.Key, TotalHoursCount = x.Sum(i => i.TotalHours) }).ToList();
}
I've changed your code to group by x.Date.Date rather than x.Date, as it seems that you want a separate grouping per day, rather than per unique time. The group by will not group the dummy data with the real data, and factor it into the sum.
You could add the dummy data afterwards but it seems easier/less work to do it beforehand.
I am trying to count no of leaves condition year and month here is my LINQ query-
leaves = ( from x in obj.Result
where (Int64.Parse(x.status) == 1 &&
((x.start_date.Month == month &&
x.start_date.Year == selectedyear) &&
(x.end_date.Year == selectedyear || x.end_date.Month == month)))
orderby x.user_id
select x).ToList();
and I am getting a perfect result but now my start_date and end_date becomes
the array of dates, in that array multiple leaves dates are stored
now I want to check my all record within that array condition year and month
here is my model example
public DateTime start_date { get; set; }
public DateTime end_date { get; set; }
public List<string> Leaves_Date { get; set; }
and here also leaves_Date would be a string so someone has an idea that how can I set array in my LINQ query
In my Entity Framework application, I have an Entity called Invoice.cs, it has various properties, but here are the ones we're concerned about for the question:
public class Invoice : IEntity
{
public int Id { get; set; }
public decimal Amount { get; set; }
public DateTime Date { get; set; }
public int OrderId { get; set; }
public virtual ICollection<Payment> Payments { get; set; }
}
I am attempting to query the database to get a list of all outstanding invoices. An outstanding invoice is the following:
If the total of the payments made against the Invoice are less than the Invoice Amount, then the invoice is Outstanding.
I'm stuck on working out if an invoice is outstanding or not in my LINQ query, which currently looks like this:
var outstandingInvoices = from inv in _context.Invoices
where !inv.IsDeleted && inv.Date >= startDate && inv.Date <= endDate
select inv;
startDate and endDate are parameters passed in to filter the result.
Another complexity with this is to do with how payments are made. The payments can be made in RMB (Chinese currency) or GBP (UK currency), and in the report I'm generating, I want all to be displayed as GBP.
So I must somehow add this logic too:
// loop through payments for each invoice, and add payment
// amount to a local variable called total
if (payment.Currency == Currency.Gbp)
{
total += payment.Amount;
} else
{
total += payment.Amount / (decimal)payment.ConversionRate;
}
The Payment.cs entity has these two properties that are of concern:
public class PaymentViewModel
{
public int Id { get; set; }
[Required]
public decimal Amount { get; set; }
[Required]
[Display(Name = "Payment Currency")]
public Currency Currency { get; set; }
[Display(Name = "Conversion Rate")]
public float ConversionRate { get; set; }
}
You are going to have an issue because of the float/decimal differences. Depending on your database provider, it might allow you to force the cast from decimal to float (or it might not). Of course, then you'll have issues of payments that are really close, but not quite the same. What if the amount / CoversionRate is 0.999999999999999999999999999 GBP when the amount was for 1 GBP? Technically it's not fully paid.
Ultimately, the conversion rate should also be a decimal, not float, but determining the precision depends on your source. Is it accurate to 5 decimal places or 7?
var outstanding = _context.Invoices
.Where(x=>x.Date >= startDate)
.Where(x=>x.Date <= endDate)
.Where(x=>!x.IsDeleted)
.Where(x=>x.Payments.Sum(p=>(float)p.Amount / p.ConversionRate) < x.Amount);
Alternatively if the total paid is within 1 GBP:
var outstanding = _context.Invoices
.Where(x=>x.Date >= startDate)
.Where(x=>x.Date <= endDate)
.Where(x=>!x.IsDeleted)
.Where(x=>x.Payments.Sum(p=>(float)p.Amount / p.ConversionRate) - x.Amount < 1);
You can use ternary operator to adjust payment amount in case it is not in GBP:
var outstandingInvoices =
from inv in _context.Invoices
let totalPayment = inv.Payments.Sum(p =>
p.Currency == Currency.Gbp ? p.Amount : p.Amount / p.ConversionRate)
where !inv.IsDeleted
&& inv.Date >= startDate && inv.Date <= endDate
&& totalPayment < inv.Amount
select inv;
I have a number of instances where I need to return a data list that uses .GroupBy. In addition to dates and integers I also need to return Boolean values, which I cannot seem to do. An example model:
public class HolidayCheckList
{
public DateTime startDate { get; set; }
public DateTime endDate { get; set; }
public int stafferId { get; set; }
public bool startTime { get; set; }
public bool endTime { get; set; }
}
Below is the controller as it is at present:
var model = _db.AnnualLeaves
.Where(r => r.StartDate <= tTE && r.EndDate >= tTS)
.GroupBy(r => r.tBooked)
.Select(m => new HolidayCheckList
{
startDate = m.Max(r => r.StartDate),
endDate = m.Max(r => r.EndDate),
stafferId = m.Min(r => r.StafferId)
});
return View(model.ToList());
This does what I need. However, in addition to the startDate and endDate I need to return the startTime and endTime, which are Boolean values, to the View. I don't know whether I need an aggregate operator I am not aware of, need to include in the .GroupBy statement or maybe need a nested query. No doubt it will be something far simpler.
I did consider changing the data type to an integer, but would like to know if there is a way of doing this "properly".
As an aside, is there a learning resource or documentation for lambda queries? I can find basic information but nothing that details how .GroupBy works, or what the aggregate operators are.
If you're certain that all of the startTime and endTime values will be the same (or you don't care), you could use .First to select the first item in the group's startTime and endTime values:
var model = _db.AnnualLeaves
.Where(r => r.StartDate <= tTE && r.EndDate >= tTS)
.GroupBy(r => r.tBooked)
.Select(m => new HolidayCheckList
{
startDate = m.Max(r => r.StartDate),
endDate = m.Max(r => r.EndDate),
stafferId = m.Min(r => r.StafferId),
startTime = m.Select(r => r.StartTime).First(),
endTime = m.Select(r => r.EndTime).First()
});
A good resource for lambdas is the Lambda Expressions article on MSDN.
The Aggregation Operations looks like a good overview of the various aggregate operations available in LINQ. Unfortunately the examples are in VB.NET though.