首页 > 解决方案 > 在一个键上加入两个列表,并用 CROSS 总结总数?

问题描述

我有两个List<AccountBalance>

AccountBalance 有一个 IDGUID和一个 BalanceDECIMAL值。

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

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();

但这似乎是一个内部连接,并且只给我“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

解决方案


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

例如,从这个答案看一下 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)
            }
);

推荐阅读