【问题标题】:JOIN two lists on a Key, and sum up totals with CROSS?在一个键上加入两个列表,并用 CROSS 总结总数?
【发布时间】:2019-11-18 01:31:49
【问题描述】:

我有两个List<AccountBalance>

AccountBalance 有一个 ID GUID 和一个 Balance DECIMAL 值。

一个清单包含所有贷方,一个清单包含所有借方。这些已被求和,因此列表中会有唯一的帐户 ID(但会相互重复。即,一个帐户将在 creditBalances 中出现一次,但可能会在两个列表中找到。 p>

var creditBalances = new List<AccountBalance>();

var debiBalances = new List<AccountBalance>();

我需要做的是返回一个列表,其中包含每个帐户的总数。

所以,我需要根据两个表的 ID 进行联接...对于每个帐户,在贷方中添加金额,并在借方中减去金额。

并非所有帐户都有贷方,也不是所有帐户都有借方,但我的结果集需要包含所有帐户。

我正在尝试什么:

 var result = creditBalances
                .Join(debitBalances, a => a.ExternalId, b => b.ExternalId, (a, b) => new AccountBalance { Id = a.ExternalId, Balance = a.amount - b.amount })
                .ToList();

但这似乎是一个 INNER 连接,并且只给我“creditBalances”有值的结果。我基本上需要做一个交叉连接?

学分:

Acc: 1, amount 10
Acc: 2, amount 5

借记:

Acc 2: amount 2
Acc 3: amount 5

应该导致:

Acc 1: 10
Acc 2: 3
Acc 3: -5

【问题讨论】:

标签: c# linq


【解决方案1】:

你可以做一个完整的外连接。

例如,看看来自this answer 的 FullOuterJoin 实现

适应您的具体情况:

public class AccountBalance
{
    public decimal amount { get; set; }
    public decimal ExternalId { get; set; }
}

// From https://stackoverflow.com/a/13503860/11981207
public static IEnumerable<TResult> FullOuterJoin<TA, TB, TKey, TResult>(
     this IEnumerable<TA> a,
     IEnumerable<TB> b,
     Func<TA, TKey> selectKeyA,
     Func<TB, TKey> selectKeyB,
     Func<TA, TB, TKey, TResult> projection,
     TA defaultA = default(TA),
     TB defaultB = default(TB),
     IEqualityComparer<TKey> cmp = null)
{
    cmp = cmp ?? EqualityComparer<TKey>.Default;
    var alookup = a.ToLookup(selectKeyA, cmp);
    var blookup = b.ToLookup(selectKeyB, cmp);

    var keys = new HashSet<TKey>(alookup.Select(p => p.Key), cmp);
    keys.UnionWith(blookup.Select(p => p.Key));

    var join = from key in keys
               from xa in alookup[key].DefaultIfEmpty(defaultA)
               from xb in blookup[key].DefaultIfEmpty(defaultB)
               select projection(xa, xb, key);

    return join;
}

// ---

var creditBalances = new List<AccountBalance> {
    new AccountBalance { ExternalId = 1, amount = 10 },
    new AccountBalance { ExternalId = 2, amount = 5 },
};

var debitBalances = new List<AccountBalance> {
    new AccountBalance {ExternalId = 2, amount = 2 },
    new AccountBalance { ExternalId = 3, amount = 5 },
};

var result = creditBalances.FullOuterJoin(debitBalances, 
        credit => credit.ExternalId,
        debit => debit.ExternalId,
        (credit, debit, key) => 
            new AccountBalance() { 
                 ExternalId = key, 
                 amount= (credit?.amount ?? 0) - (debit?.amount ?? 0)
            }
);

【讨论】:

  • 谢谢@Kei!这解决了我的问题。现在尝试弄清楚该扩展方法在做什么!感谢您的帮助!
  • 据我了解,首先该方法根据连接键创建一个lookup。然后它创建一个两边都有键的 HashSet(集合 A 中的键与集合 B 中的键联合)。最后,它遍历每个键。对于每个键,它从每个集合中选择键和一个对应项(如果没有,则选择默认值;对于引用类型,默认值为 null)。
  • 如果有多个项目与键相关,则查询将添加额外的行(因为它在 keyxaxb 之间进行交叉连接,其中 xa 和 xb 表示集合 A 和 B 中的所有匹配项)。我刚刚意识到,如果您可能有多个具有相同类型(贷记/借记)和 ExternalId 的余额,这可能不是您想要的。例如,如果您的集合中有两次借方余额 Acc 3: amount 5,则 Acc 3: -5 也会输出两次。
  • 如果ExternalIds 可能重复,请在链接答案中使用FullOuterGroupJoin,并将我的答案中的amount= (credit?.amount ?? 0) - (debit?.amount ?? 0) 更改为amount = (credit?.Sum(x =&gt; x.amount) ?? 0) - (debit?.Sum(x =&gt; x.amount) ?? 0)
猜你喜欢
  • 1970-01-01
  • 2019-01-19
  • 2021-02-18
  • 1970-01-01
  • 1970-01-01
  • 2011-07-26
  • 1970-01-01
  • 2018-08-02
  • 1970-01-01
相关资源
最近更新 更多