C# GroupBy with或composite key not AND composite

C# GroupBy with或composite key not AND composite,c#,linq,C#,Linq,我有一个特例,我试着将付款与发票相匹配。我用一个标准的左连接 现在我得到了一份发票付款清单。但我需要将其分组以验证输入 我需要得到一个发票清单连接到付款清单 一张发票多笔付款可以,但多张发票多笔付款不行 如果我执行类似于GroupBy(leftJoin=>new{leftJoin.payment.Id,leftJoin.invoice?.Id})的组合键组这将不起作用,它将返回与连接基本相同的结果 GroupBy(leftJoin=>leftJoin.payment.Id)这将适用于一次付款多张

我有一个特例,我试着将付款与发票相匹配。我用一个标准的左连接

现在我得到了一份发票付款清单。但我需要将其分组以验证输入

我需要得到一个发票清单连接到付款清单

一张发票多笔付款可以,但多张发票多笔付款不行

如果我执行类似于
GroupBy(leftJoin=>new{leftJoin.payment.Id,leftJoin.invoice?.Id})的组合键组
这将不起作用,它将返回与连接基本相同的结果

GroupBy(leftJoin=>leftJoin.payment.Id)
这将适用于一次付款多张发票的情况。但对于许多发票人来说,同一笔付款是不可能的

GroupBy(leftJoin=>leftJoin.invoice?.Id)
这适用于一张发票到多张付款,但会将不匹配的付款分组到一个组中(不是什么大问题),并且一张付款到多张发票会中断

我需要使用复合键,但它需要是键之间的OR关系。不,还有,想法

编辑:根据请求编辑整个方法

public async Task MatchInvoicesWithPaymentsAsync(IEnumerable<InvoiceReadInfo> invoices, IEnumerable<PaymentReadInfo> payments)
    {
        var result = payments
            .SelectMany(p => invoices.Where(i => p.Reference == i.Ocr).DefaultIfEmpty(), (p, i) => new { p, i })
            .GroupBy(leftJoin => leftJoin.p.Id) //<--- problem goes here method GetMatchJob below takes a list of payments and a list of invoices 
            .Select(grp => GetMatchJob(grp.Select(info => info.p).ToList(), grp.Select(info => info.i).Where(i => i != null).Distinct().ToList()))
            .Select(job => _handlers[job.MatchOutcome](job))
            .Where(r => r != null)
            .ToList();

        await ProcessMatches(result);
    }
公共异步任务匹配InvoicesWithPaymentsAsync(IEnumerable发票,IEnumerable付款)
{
var结果=付款
.SelectMany(p=>invoices.Where(i=>p.Reference==i.Ocr).DefaultIfEmpty(),(p,i=>new{p,i})
.GroupBy(leftJoin=>leftJoin.p.Id)//GetMatchJob(grp.Select(info=>info.p).ToList(),grp.Select(info=>info.i).其中(i=>i!=null).Distinct().ToList())
.Select(作业=>\u处理程序[job.MatchOutcome](作业))
.其中(r=>r!=null)
.ToList();
等待进程匹配(结果);
}
编辑:在评论中讨论后的解决方案

private class GroupByComparer : IEqualityComparer<InvoicePayment>
{
    public bool Equals(InvoicePayment x, InvoicePayment y)
    {
        if (x.Invoice == null ||    y.Invoice == null)      return true;
        if (x.Invoice.InvoiceId ==  y.Invoice.InvoiceId)    return true;
        if (x.Payment.Id ==         y.Payment.Id)           return true;
        
        return false;
    }

    public int GetHashCode(InvoicePayment obj)
    {
        return 0;
    }
}
私有类GroupByComparer:IEqualityComparer
{
公共bool等于(发票付款x,发票付款y)
{
如果(x.Invoice==null | | y.Invoice==null)返回true;
如果(x.Invoice.InvoiceId==y.Invoice.InvoiceId)返回true;
如果(x.Payment.Id==y.Payment.Id)返回true;
返回false;
}
公共int GetHashCode(发票支付对象)
{
返回0;
}
}

您应该能够为接受
IEqualityComparer的
GroupBy
使用重载

根据键选择器功能对序列的元素进行分组。使用比较器比较键,并使用指定函数投影每个组的元素

您基本上可以实现如下内容:

public class CompositeGroupingKeyComparer : IEqualityComparer<InvoicePayment>
{
    public bool Equals(InvoicePayment x, InvoicePayment y)
    {
        // Handle your OR logic here        
        return 
            x.Invoice?.InvoiceId == y.Invoice?.InvoiceId || // Not fully convinced by 2 nulls matching though perhaps you need to only match if both sides have an invoice?
            x.Payment.Id == y.Payment.Id;
    }

    // This part is key as you require the grouping to identify the same hash codes and then deal with Equals method.
    public int GetHashCode(InvoicePayment obj)
    {
        return 0;
    }

}
公共类CompositeGroupingKeyComparer:IEqualityComparer
{
公共bool等于(发票付款x,发票付款y)
{
//在这里处理你的逻辑
返回
x、 Invoice?.InvoiceId==y.Invoice?.InvoiceId | |///两个空值的匹配并不完全令人信服,尽管您可能只需要在双方都有发票的情况下进行匹配?
x、 Payment.Id==y.Payment.Id;
}
//这一部分很关键,因为您需要分组来标识相同的哈希代码,然后处理Equals方法。
公共int GetHashCode(发票支付对象)
{
返回0;
}
}

为什么发票可以为空?如果发票与付款不匹配。(左连接)那么这些不应该在前面使用
完全从查询中排除。其中(pmt=>pmt.invoice!=null)
?当然,您可以使用
GroupBy
的重载并传入您自己的
IEqualityComparer
Dude。你很少使用它们,所以你会忘记它们。我添加了我的解决方案,请写一个答案,我接受我的IEqualityComparer示例运行良好!感谢ideaAs的旁注GetHashCode必须返回相同的值,否则它将无法工作。@Anders很高兴听到它的帮助!这是一个非常好的观点,您可以这样说:
GetHashCode
,我怀疑它总是必须匹配才能允许相等比较工作?它执行hashcode检查,以便能够将结果快速存储在例如字典中。应该是o(n)复杂度。用我的解决方案,它可能是o(n^2)复杂度。但是哈希代码并不能满足我的用例。