我想将最早的信用发票日期映射到销售表中的列表。我有以下方式的列表。
var tb = // Some other method to get balances.
cust balance
1 1000
2 2000
3 3000
... so on ...
这些余额是几张发票的累积,有时可能是一张发票。
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...
}
客户 1 的示例数据
saleInvNo saleDate cust invoiceValue
1 2018/12/01 1 500
12 2018/12/20 1 750
如果我现在获取,报告应该如下。
cust balance balanceFromDate
1 1000 2018/12/01 // balance from this onwards.
2 2000 ???
3 3000 ???
有没有简单的方法可以通过 LINQ 实现这一点。
我尝试使用 TakeWhile 但未成功。
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; });
}
注意:Sales 表有大约 75K 条记录。
更多信息...
客户可能会支付销售发票以外的部分金额。所有付款、发票都发布到另一个表,这就是为什么余额来自对另一个表的复杂查询。
Sales 表包含纯原始销售数据。
现在,客户 1 的余额比上一张销售发票的值(value)甚至最后一张销售发票的全部或部分值(value)多 1000。我需要映射“从开始的平衡”。
最佳答案
因此您有两个序列:Balances
序列和 Sales
序列。
每个 Balance
至少有一个 int 属性 CustomerId
和一个 BalanceValue
每个 Sale
至少有一个可为 null 的 int 属性 CustomerId 和一个 DateTime 属性 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;}
}
显然,没有客户也可以进行销售。
现在给定一系列 Balances
和 Sales
,您希望每个 Balance
都有一个对象,其中包含 CustomerId
、此客户在 Sales
序列中的 BalanceValue
和 SaleDate
。
您的要求没有指定您想要的余额序列中的余额,这些余额在销售序列中有一个没有销售的客户。假设您希望结果中的这些项目具有 null LastSaleDate
。
IEnumerable<Balance> balances = ...
IEnumerable<Sales> sales = ...
我们对没有客户的销售不感兴趣。所以让我们先过滤掉它们:
var salesWithCustomers = sales.Where(sale => sale.CustomerId != null);
现在通过 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
})
LastSaleDate
的计算对所有元素进行排序,并仅保留第一个。
虽然此解决方案有效,但如果您只需要最大的一个,则对所有其他元素进行排序效率不高。如果您正在使用 IEnumerables 而不是 IQueryables(就像在数据库中那样),您可以通过创建一个函数来优化它。
我将其实现为扩展功能。参见 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;
}
}
用法:
LastSaleDate = salesOfBalance
.Select(sale => sale.SaleDate)
.NewesDateOrDefault();
现在您知道了,您无需对整个序列进行排序,只需枚举序列一次即可。
注意:您不能为此使用 Enumerable.Aggregate,因为它不适用于空序列。
关于c# - 如何使用 LINQ TakeWhile 和加入列表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53868890/