Linq查询给出不适当的输出

时间:2016-01-12 06:39:28

标签: c# asp.net linq linq-group

我有两个名为 ParentTransaction ChildTransaction 的交易表,其中 ParentTransaction TransactionId将对 {Trantaction TransactionId

现在我想得到那些payamount未完成的ParentTransaction的TransactionId。

从下面输出我想要事务ID 3的记录,因为只有1000 transactionid 3而不是5000。

我有一张这样的表:

Transactionid(p.k)    PayAmount
  1                   1000
  2                   3000
  3                   5000
  4                   6000

ChildTransaction

Id        TransactionId(F.k)   DepositAmount
1           1                  600
2           1                  400
3           2                  1000
4           2                  1000
5           2                  1000
6           3                  2000

这是我的疑问:

var data = (from tmp in context.ParentTransaction
            join tmp1 in context.ChildTransaction on tmp.Transactionid equals
            tmp1.Transactionid where tmp.PayAmount !=tmp1.DepositAmount
                    select tmp);

但是在这里我得到了交易ID 1和2,尽管他们的交易已经完成了两个部分,即交易ID 1的600和400.

5 个答案:

答案 0 :(得分:13)

查询语言的一般概念是表达期望的结果,而不是如何获得它。

将它应用于您的场景会导致像这样的简单查询

var query = context.ParentTransaction
   .Where(t => t.PayAmount != context.ChildTransaction
      .Where(ct => ct.TransactionId == t.TransactionId)
      .Sum(ct => ct.DepositAmount));

如果您使用的是 EF 和正确的模型导航属性,那就更简单了

var query = context.ParentTransaction
    .Where(t => t.PayAmount != t.ChildTransactions.Sum(ct => ct.DepositAmount));

有人可能会说,与让@Vadim Martynov的回答相比,上述内容效率低下。好吧,可能是的,可能不是。 Vadim试图强制执行特定的执行计划,我可以理解 - 我们必须在实际遇到查询性能问题时执行此类操作。但它不自然,只有在遇到性能问题时才应该是最后的手段。在大多数情况下,查询提供程序和SQL查询优化器将为我们完成(并且正在执行)该工作,因此我们不需要考虑是否需要使用join vs {{1}等等。

答案 1 :(得分:9)

我不确定!=是否是最佳价值。以下是>检查和分组的解决方案:

var expectedValue =
            context.ChildTransaction
                .GroupBy(t => t.TransactionId, (key, group) => new { TransactionId = key, Deposit = group.Sum(e => e.Deposit) })
                .Join(context.ParentTransaction, grouped => grouped.TransactionId, transaction => transaction.TransactionId, (group, transaction) => new { Transaction = transaction, group.Deposit })
                .Where(result => result.Transaction.PayAmount > result.Deposit)
                .Select(result => result.Transaction);

此查询可以按照下一个要求的声明方式阅读:

  1. GroupTransactionId收集子事务,并为每个组检索anonymous type个对象,其中包含字段TransactionId =分组键(== TransactionId)和存款,它是行的存款总和使用相同的TransactionId。
  2. Join通过PaerntTransaction字段从第1部分设置到表TransactionId。对于每个联接对,检索一个anonymous type对象,其中包含来自ParentTransactions表的字段Transaction == transaction和来自第1部分的存款,这是来自{TransactionId的存款之和。 {1}}表。
  3. 仅从结果集中过滤掉PayAmount比存款金额更大的对象。
  4. 仅为每个已过滤的行返回ChildTransactions个对象。
  5. 这是SQL优化的方案,因为连接,过滤和分组会阻止nested queries在其他情况下可以添加到实际执行计划中并使性能更差。

    <强>更新

    要解决没有存款的交易问题,您可以使用LEFT JOIN

    ParentTransaction

    与LINQ方法链相同的查询:

    var expectedValue = from parent in context.ParentTransaction
                join child in context.ChildTransaction on parent.TransactionId equals child.TransactionId into gj
                from subset in gj.DefaultIfEmpty()
                let joined = new { Transaction = parent, Deposit = subset != null ? subset.Deposit : 0 }
                group joined by joined.Transaction
                into grouped
                let g = new { Transaction = grouped.Key, Deposit = grouped.Sum(e => e.Deposit) }
                where g.Transaction.PayAmount > g.Deposit
                select g.Transaction;
    

    现在您检索所有父事务并将其与子事务连接,但如果没有子事务,则使用var expectedValue = context.ParentTransaction .GroupJoin(context.ChildTransaction, parent => parent.TransactionId, child => child.TransactionId, (parent, gj) => new { parent, gj }) .SelectMany(@t => @t.gj.DefaultIfEmpty(), (@t, subset) => new { @t, subset }) .Select(@t => new { @t, joined = new { Transaction = @t.@t.parent, Deposit = @t.subset != null ? @t.subset.Deposit : 0 } }) .GroupBy(@t => @t.joined.Transaction, @t => @t.joined) .Select(grouped => new { grouped, g = new { Transaction = grouped.Key, Deposit = grouped.Sum(e => e.Deposit) } }) .Where(@t => @t.g.Transaction.PayAmount > @t.g.Deposit) .Select(@t => @t.g.Transaction); 并按Deposit == 0以类似方式对连接的实体进行分组。

答案 2 :(得分:4)

问题

问题出在这个声明上:

where tmp.PayAmount != tmp1.DepositAmount //the culprit

由于tmp1被定义为单个子事务,因此该语句会导致错误的值等于:

<强>展示台

1000 != 600 //(result: true -> selected) comparing parent 1 and child 1
1000 != 400 //(result: true -> selected) comparing parent 1 and child 2
3000 != 1000 //(result: true -> selected) comparing parent 2 and child 3
3000 != 1000 //(result: true -> selected) comparing parent 2 and child 4
3000 != 1000 //(result: true -> selected) comparing parent 2 and child 5
5000 != 2000 //(result: true -> selected) comparing parent 2 and child 5
//However, you do not want it to behave like this actually

但你想拥有的是:

<强>展示台

1000 != (600 + 400) //(result: false -> not selected) comparing parent 1 and child 1 & 2, based on the TransactionId
3000 != (1000 + 1000 + 1000) //(result: false -> not selected) comparing parent 2 and child 3, 4, & 5, based on the TransactionId
5000 != (2000)  //(result: true -> selected) comparing parent 3 and child 6, based on the TransactionId 
6000 != nothing paid //(result: true -> selected) comparing parent 3 with the whole childTransaction and found there isn't any payment made

因此,您应该将tmp1作为集合的子项而不是单个子项。

解决方案

未付交易

更改您的代码:

var data = (from tmp in context.ParentTransaction
            join tmp1 in context.ChildTransaction.GroupBy(x => x.TransactionId) //group this by transaction id
            on tmp.TransactionId equals tmp1.Key //use the key
            where tmp.PayAmount > tmp1.Sum(x => x.DepositAmount) //get the sum of the deposited amount
            select tmp)
           .Union( //added after edit
           (from tmp in context.ParentTransaction
            where !context.ChildTransaction.Select(x => x.TransactionId).Contains(tmp.TransactionId)
            select tmp)
           );                               

<强>说明

这一行:

join tmp1 in context.ChildTransaction.GroupBy(x => x.TransactionId) //group this by transaction id

利用GroupBy中的Linq,此行使tmp1成为群组的孩子,而不是单身子女,理所当然,基于其外键,即TransactionId

然后这一行:

on tmp.TransactionId equals tmp1.Key //use the key

我们只是将tmp.TransactionId与孩子的群组密钥tmp1.Key

等同起来

然后是下一行:

where tmp.PayAmount > tmp1.Sum(x => x.DepositAmount) //get the sum of the deposited amount

获取孩子的DepositAmount而不是单个孩子的DepositAmount的总和值,该值小于父亲中的PayAmount,然后

select tmp

选择满足上述所有条件的所有父交易。这样,我们完成了一半。

下一步是考虑在父级而非子级(ren)中发生的事务。这也被认为是无偿的。

我们可以使用query

将第一个query的结果与第二个Union的结果相结合
.Union( //added after edit
(from tmp in context.ParentTransaction
 where !context.ChildTransaction.Select(x => x.TransactionId).Contains(tmp.TransactionId)
 select tmp)
);                              

这会选择父交易中存在的任何内容,但在子交易中根本不存在(因此被视为未支付)。

您将获得正确的data,其中包含未完全付款的ParentTransaction行,这两行都适用于子级中存在TransactionId的父级交易。

付费交易

对于付费交易,只需将查询从>更改为<=

var datapaid = (from tmp in context.ParentTransaction
                join tmp1 in context.ChildTransaction.GroupBy(y => y.TransactionId)
                on tmp.TransactionId equals tmp1.Key
                where tmp.PayAmount <= tmp1.Sum(x => x.DepositAmount)
                select tmp);

联合

我们可以进一步简化上述查询:

var grp = context.ChildTransaction.GroupBy(y => y.TransactionId);
var data = (from tmp in context.ParentTransaction
            join tmp1 in grp //group this by transaction id
            on tmp.TransactionId equals tmp1.Key //use the key
            where tmp.PayAmount > tmp1.Sum(x => x.DepositAmount)
            select tmp)
            .Union((
            from tmp in context.ParentTransaction
            where !context.ChildTransaction.Select(x => x.TransactionId).Contains(tmp.TransactionId)
            select tmp));

var datapaid = (from tmp in context.ParentTransaction
                join tmp1 in grp
                on tmp.TransactionId equals tmp1.Key
                where tmp.PayAmount <= tmp1.Sum(x => x.DepositAmount)
                select tmp);

答案 3 :(得分:2)

    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
        <version>1.1.0.Final</version>
    </dependency>


    <dependency>
        <groupId>org.hibernate</groupId>
        <artifactId>hibernate-validator</artifactId>
        <version>5.2.2.Final</version>
    </dependency>

答案 4 :(得分:2)

您只能控制一个子事务。您必须使用Sum()操作,并且需要使用>代替!=请尝试此操作。

var data = (from tmp in context.ParentTransaction
            join tmp1 in context.ChildTransaction on tmp.Transactionid equals into tmp1List
            tmp1.Transactionid where tmp.PayAmount > tmp1List.Sum(l => l.DepositAmount)
            select tmp);