如何使用LINQ TakeWhile并加入列表

时间:2018-12-20 12:37:52

标签: c# linq entity-framework-core-2.1

我想将最旧的信用发票日期映射到销售表中的列表。我有以下列表。

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表具有约75,000条记录。

更多信息...

客户可以比销售发票支付部分款项。所有付款和发票都过帐到另一个表,这就是为什么余额来自另一个表的复杂查询。

“销售”表仅包含原始销售数据。

现在,客户1的余额比上次销售发票甚至全部或部分最后一次销售发票的值都多1000。我需要映射“从平衡开始”。

1 个答案:

答案 0 :(得分:0)

因此,您有两个序列: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;}
} 

显然,没有客户就可以进行销售。

现在给定BalancesSales的序列,您想要每个Balance一个对象,其中包含CustomerIdBalanceValue和{{ 1}},该客户具有SaleDate的顺序。

您的要求未按在“销售”序列中有“无销售”客户的“余额”序列中指定您想要的“余额”。假设您希望结果中的项目为空Sales

LastSaleDate

没有客户,我们对销售不感兴趣。因此,让我们先将它们过滤掉:

IEnumerable<Balance> balances = ...
IEnumerable<Sales> sales = ...

现在,通过在CustomerId上加入group,以其销售来创建余额组:

var salesWithCustomers = sales.Where(sale => sale.CustomerId != null);

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 }) 的计算将所有元素排序,并且仅保留第一个元素。

尽管此解决方案有效,但如果只需要最大的元素,则订购所有其他元素效率不高。如果您使用的是IEnumerables,而不是IQueryables(就像在数据库中一样),则可以通过创建函数来对此进行优化。

我将其实现为扩展功能。参见extension methods demystified

LastSaleDate

用法:

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;
    }
}

现在您知道,除了对整个序列进行排序之外,您只需要枚举一次即可。

注意:您不能为此使用Enumerable.Aggregate,因为它不适用于空序列。