I want to map oldest possible credit invoice's date to a list from sale table. I have a list in following manner.
var tb = // Some other method to get balances.
cust balance
1 1000
2 2000
3 3000
... so on ...
These balance are accumulation of few invoices or sometimes may be one invoice.
public class Sales
{
public int id { get; set; }
public DateTime saleDate { get; set; }
public int? cust { get; set; }
public decimal invoiceValue { get; set; }
// Other properties...
}
Sample data for cust 1
saleInvNo saleDate cust invoiceValue
1 2018/12/01 1 500
12 2018/12/20 1 750
If I fetch now, report should be as follows.
cust balance balanceFromDate
1 1000 2018/12/01 // balance from this onwards.
2 2000 ???
3 3000 ???
Is there easy way to achieve this through LINQ.
I tried with TakeWhile but attempt was not successful.
foreach (var t in tb)
{
var sum = 0m;
var q = _context.SaleHeaders.Where(w=>w.cust == t.cust)
.OrderByDescending(o=>o.saleDate)
.Select(s => new { s.id, s.saleDate, s.invoiceValue }).AsEnumerable()
.TakeWhile(x => { var temp = sum; sum += x.invoiceValue; return temp > t.balance; });
}
Note: Sales table is having ~75K records.
More Info...
Customers may pay partial amount than Sale Invoice. All Payments, Invoices are posted to another table thats why balances comes from a complex query on another table.
Sales table has purely raw sales data.
Now, cust 1 balance is 1000 than the value is of last sale invoice or even last to last sale invoice's full or partial. I need to map "balance from onwards".
So you have two sequences: a sequence of Balances
and a sequence of Sales
.
Every Balance
has at least an int property CustomerId
, and a BalanceValue
Every Sale
has at least a nullable int property CustomerId and a DateTime property SaleDate
class Balance
{
public int CustomerId {get; set;}
public decimal BalanceValue {get; set;}
}
class Sale
{
public int? CustomerId {get; set;}
public DateTime SaleDate {get; set;}
}
Apparently it is possible to have a Sale without a Customer.
Now given a sequence of Balances
and Sales
, you want for every Balance
one object, containing the CustomerId
, the BalanceValue
and the SaleDate
that this Customer has in the sequence of Sales
.
Your requirement doesn't specify what you want with Balances in your sequence of Balances that have a Customer without a Sale in the sequence of Sales. Let's assume you want those items in your result with a null LastSaleDate
.
IEnumerable<Balance> balances = ...
IEnumerable<Sales> sales = ...
We are not interested in sales without a customer. So let's filter them out first:
var salesWithCustomers = sales.Where(sale => sale.CustomerId != null);
Now make groups of Balances with their sales, by groupjoiningon CustomerId:
var balancesWithSales = balances.GroupJoin(salesWithCustomers, // GroupJoin balances and sales
balance => balance.CustomerId, // from every Balance take the CustomerId
sale => sale.CustomerId, // from every Sale take the CustomerId. I know it is not null,
(balance, salesOfBalance) => new // from every balance, with all its sales
{ // make one new object
CustomerId = balance.CustomerId,
Balance = balance.BalanceValue,
LastSaleDate = salesOfBalance // take all salesOfBalance
.Select(sale => sale.SaleDate) // select the saleDate
.OrderByDescending(sale => sale.SaleDate) // order: newest dates first
.FirstOrDefault(), // keep only the first element
})
The calculation of the LastSaleDate
orders all elements, and keeps only the first.
Although this solution workd, it is not efficient to order all other elements if you only need the largest one. If you are working with IEnumerables, instead of IQueryables (as would be in a database), you can optimize this, by creating a function.
I implement this as an extension function. See extension methods demystified
static DateTime? NewestDateOrDefault(this IEnumerable<DateTime> dates)
{
IEnumerator<DateTime> enumerator = dates.GetEnumerator();
if (!enumerator.MoveNext())
{
// sequence of dates is empty; there is no newest date
return null;
}
else
{ // sequence contains elements
DateTime newestDate = enumerator.Current;
while (enumerator.MoveNext())
{ // there are more elements
if (enumerator.Current > newestDate)
newestDate = enumerator.Current);
}
return newestDate;
}
}
Usage:
LastSaleDate = salesOfBalance
.Select(sale => sale.SaleDate)
.NewesDateOrDefault();
Now you know that instead of sorting the complete sequence you'll enumerate the sequence only once.
Note: you can't use Enumerable.Aggregate for this, because it doesn't work with empty sequences.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.