c# - 如何使用 LINQ TakeWhile 和加入列表

标签 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 表有大约 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;}
} 

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

现在给定一系列 BalancesSales,您希望每个 Balance 都有一个对象,其中包含 CustomerId、此客户在 Sales 序列中的 BalanceValueSaleDate

您的要求没有指定您想要的余额序列中的余额,这些余额在销售序列中有一个没有销售的客户。假设您希望结果中的这些项目具有 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/

相关文章:

c# - linq to sql 和 DataContext 中的隔离级别

c# - 用于检索所有子节点的 Linq-to-XML

dapper - 使用 .NET Core 2.1 中的 SQL Server 的 Always Encrypted

asp.net-core - 什么可能导致 `UserManager` 返回错误的用户?

c# - 设置查询结果对象的小数精度

c# - 为什么 System.Array 不能是类型约束?

c# - Msbuild 使用唯一文件名复制和展平

c# - 如何从另一个线程更新 GUI?

c# - 返回不同的 HTTP 状态码进行测试

c# - 如何在 C# 中创建基于 DataTable.Rows.Count 的对象?