【问题标题】:LINQ Group by on multiple tables with nested group by with join and aggregateLINQ Group by 在多个表上使用嵌套 group by 和连接和聚合
【发布时间】:2017-06-08 21:55:44
【问题描述】:

我有以下三个模型。我想在我的 MVC 应用程序中使用 LINQ 将它们加入并分组,以获得所需的结果数据。

尝试使用错误的数据

var query = from i in db.Invoices
                    join id in db.Invoice_Details
                    on i.INVOICENO equals id.INVOICENO
                    join m in db.Mixings
                    on id.INVOICEDETAILID equals m.INVOICEDETAILID into ms
                    from m in ms.DefaultIfEmpty()
                    group new { id,m } by new
                    {
                        INVOICENO = id.INVOICENO,
                        DATE = i.DATE


                    }
                    into temp
                    select new Invoice_List
                    {
                        ID = temp.Key.INVOICENO,
                        INVOICENO = temp.Key.INVOICENO,
                        CARET = temp.Sum(g => g.id.CARET),
                        DATE = temp.Key.DATE,
                        ISSUECARET = (decimal?)temp.Select(c => c.m.CARETUSED).DefaultIfEmpty(0).Sum() ?? 0,
                        AVAILABLECARET = ((decimal?)temp.Select(c => c.id.CARET).DefaultIfEmpty(0).Sum() ?? 0) - ((decimal?)temp.Select(c => c.m.CARETUSED).DefaultIfEmpty(0).Sum() ?? 0)
                    };

发票表

INVOICENO        DATE
---------------------
1                2017-01-23 00:00:00
2                2017-01-23 00:00:00

发票明细表

INVOICEDETAILID        INVOICENO        CARET
----------------------------------------------
1                      1                100.00
2                      1                200.00
3                      2                300.00
4                      2                400.00  

混合表

MIXINGNO        INVOICEDETAILID        CARETUSED
------------------------------------------------
1               1                      50.00
1               2                      100.00                
2               1                      25.00
2               2                      50.00

现在我想通过将这三个表与 group by 连接来跟踪结果数据。

预期结果

INVOICENO        DATE                        TOTALCARET          CARETUSEDCARET        AVAILABLECARET
------------------------------------------------------------------------------------------
1                2017-01-23 00:00:00         300.00              225.00                75.00
2                2017-01-23 00:00:00         700.00              0.00                  700.00

错误的结果(1 个 INVOICENO 的 TOTALCARET)

INVOICENO        DATE                        TOTALCARET          CARETUSEDCARET        AVAILABLECARET
------------------------------------------------------------------------------------------
1                2017-01-23 00:00:00         600.00              225.00                375.00
2                2017-01-23 00:00:00         700.00              0.00                  700.00

【问题讨论】:

  • 您的问题在哪里?什么不起作用?
  • 能否显示表格发票明细表的字段ID
  • 我在TOTALCARET 列的结果中得到错误的聚合数据
  • 您正在加入Mixings 表,这意味着当它包含多行时,一些Invoice_Details 将返回多个。你可能总和太多了。你的TOTALCARET 值是不是太高了?您可能会失去混合加入并在分组后加入它
  • 您应该在分组后加入db.Mixings。如您所见,混合表包含 2 行,这就是您的结果是两倍的原因。您没有在群组中使用m,因此您可以稍后加入。太糟糕了,我不能自己测试它,因为我需要自己跟踪和错误。所以我不能直接为您提供一个工作示例。 您可以先从 linq 查询中删除混合表。

标签: c# entity-framework linq asp.net-mvc-5


【解决方案1】:

我写了一个示例作为对我们拥有的 cmets 的回复,这可能无法解决您的所有问题,但可能是一个好的开始。这是我的测试环境:

public class Invoice
{
    public int InvoiceNo { get; set; }
    public DateTime DateTime { get; set; }
}


public class InvoiceDetails
{
    public int InvoiceDetailId { get; set; }
    public int InvoiceNo { get; set; }
    public decimal Caret { get; set; }
}

public class Mixing
{
    public int MixingNo { get; set; }
    public int InvoiceDetailId { get; set; }
    public decimal CaretUsed { get; set; }

}

private static void ExecQuery()
{
    var invoices = new List<Invoice>();
    invoices.Add(new Invoice { InvoiceNo = 1, DateTime = new DateTime(2017, 1, 23) });
    invoices.Add(new Invoice { InvoiceNo = 2, DateTime = new DateTime(2017, 1, 23) });

    var invoiceDetails = new List<InvoiceDetails>();
    invoiceDetails.Add(new InvoiceDetails { InvoiceDetailId = 1, InvoiceNo = 1, Caret = 100 });
    invoiceDetails.Add(new InvoiceDetails { InvoiceDetailId = 2, InvoiceNo = 1, Caret = 200 });
    invoiceDetails.Add(new InvoiceDetails { InvoiceDetailId = 3, InvoiceNo = 2, Caret = 300 });
    invoiceDetails.Add(new InvoiceDetails { InvoiceDetailId = 4, InvoiceNo = 2, Caret = 400 });

    var mixings = new List<Mixing>();
    mixings.Add(new Mixing { MixingNo = 1, InvoiceDetailId = 1, CaretUsed = 50 });
    mixings.Add(new Mixing { MixingNo = 2, InvoiceDetailId = 2, CaretUsed = 100 });
    mixings.Add(new Mixing { MixingNo = 3, InvoiceDetailId = 1, CaretUsed = 25 });
    mixings.Add(new Mixing { MixingNo = 4, InvoiceDetailId = 2, CaretUsed = 50 });


    // select all from invoices
    var query = from i in invoices
                // join the details
                join id in invoiceDetails on i.InvoiceNo equals id.InvoiceNo
                // group the details on invoice
                group id by new { i.InvoiceNo, i.DateTime } into ig

                // again join the details (from the mixing)
                join id in invoiceDetails on ig.Key.InvoiceNo equals id.InvoiceNo
                // join the mixing
                join mix in mixings on id.InvoiceDetailId equals mix.InvoiceDetailId into mix2 // store in temp for outer join
                from mbox in mix2.DefaultIfEmpty()
                // group mixing (and sum the caret of the previous group
                group mbox by new { ig.Key.InvoiceNo, ig.Key.DateTime, TotalCaret = ig.Sum(item => item.Caret) } into igm
                // calculate the caret used (because it is used twice in the results)
                let caretUsedCaret = igm.Where(item => item != null).Sum(item => item.CaretUsed)
                // select the results.
                select new
                {
                    igm.Key.InvoiceNo,
                    igm.Key.DateTime,
                    igm.Key.TotalCaret,
                    CaretUsedCaret = caretUsedCaret,
                    Available = igm.Key.TotalCaret - caretUsedCaret
                };


    foreach (var row in query)
    {
        Trace.WriteLine(row.ToString());
    }

}

显示哪些结果:

{ InvoiceNo = 1, DateTime = 23-Jan-17 00:00:00, TotalCaret = 300, CaretUsedCaret = 225, Available = 75 }
{ InvoiceNo = 2, DateTime = 23-Jan-17 00:00:00, TotalCaret = 700, CaretUsedCaret = 0, Available = 700 }

【讨论】:

  • 这给了我以下错误The nested query is not supported. Operation1='GroupBy' Operation2='MultiStreamNest'
  • 这可能是 linq2objects 与 linq2sql 的区别。我这里没有 sql,所以我无法验证/解决它。您可能会为此在 stackoverflow 上创建一个问题,或者您应该找到另一种方法。
【解决方案2】:

EF 的最大特点之一是所谓的导航属性。在 LINQ to Entities 查询中使用时,它们提供必要的元数据以在将查询转换为 SQL 时构建必要的连接。并允许您构建查询,例如它们是否在对象上运行,这基本上消除了考虑连接的需要,而是专注于您的逻辑。

假设您的模型是这样的(仅显示导航属性):

public class Invoice
{
    // ...
    public ICollection<InvoiceDetail> Details { get; set; }
}

public class InvoiceDetail
{
    // ...
    public ICollection<Mixing> Mixings { get; set; }
}

同时查看表格,似乎InvoiceNoInvoice 的PK。

在这种情况下,您甚至不需要GroupBy。前 2 个字段来自 Invoice,另一个是从 children 用 Sum 检索的:

var query =
    from i in db.Invoices
    let TOTALCARET = i.Details.Sum(d => (decimal?)d.CARET) ?? 0
    let USEDCARET = i.Details.SelectMany(d => d.Mixings).Sum(m => (decimal?)m.CARETUSED) ?? 0
    select new
    {
        i.INVOICENO,
        i.DATE,
        TOTALCARET,
        USEDCARET,
        AVAILABLECARET = TOTALCARET - USEDCARET
    };

唯一的技巧是在使用Sum函数时将不可为空的类型提升为可空,以避免源序列为空时的异常。然后在需要时使用 null-coalescing 运算符将其转回不可为空。

【讨论】:

  • 是的,您的解决方案运行良好。谢谢你。没有模型之间的关系可以做到吗?
  • 当然有可能,但是如果你有一个简单的方法,为什么还要麻烦:) 但是如果你有兴趣,用db.Invoice_Details.Where(d =&gt; d.INVOICENO == i.INVOICENO)d.Mixings 替换d.Mixingsdb.Mixings.Where(m =&gt; m.INVOICEDETAILID == d.INVOICEDETAILID) 就可以了。
  • 会调查的
【解决方案3】:

哈哈 :) 我用方法链写了同样的东西......

   public class Invoice
    {
        public int INVOICENO { get; set; }
        public DateTime DATE { get; set; }
    }

    public class InvoiceDetail
    {
        public int INVOICEDETAILID { get; set; }
        public int INVOICENO { get; set; }
        public int CARET { get; set; }
    }

    public class Mixing
    {
        public int MIXINGNO { get; set; }
        public int INVOICEDETAILID { get; set; }
        public int CARETUSED { get; set; }
    }

    [Fact]
    public void LinqTest()
    {
        List<int>  ints = new List<int> {1,2,3};

        List<Invoice> invoices = new List<Invoice>
        {
            new Invoice {INVOICENO = 1, DATE = DateTime.Parse("23/01/2017")},
            new Invoice {INVOICENO = 2, DATE = DateTime.Parse("23/01/2017")}
        };

        List<InvoiceDetail> invoiceDetails = new List<InvoiceDetail>
        {
            new InvoiceDetail{ INVOICEDETAILID = 1, INVOICENO = 1, CARET = 100},
            new InvoiceDetail { INVOICEDETAILID = 2, INVOICENO = 1, CARET = 200},
            new InvoiceDetail { INVOICEDETAILID = 3, INVOICENO = 2, CARET = 300},
            new InvoiceDetail {INVOICEDETAILID = 4, INVOICENO = 2, CARET = 400}
        };

        List<Mixing> mixings = new List<Mixing>
        {
            new Mixing {MIXINGNO = 1, INVOICEDETAILID = 1, CARETUSED = 50},
            new Mixing {MIXINGNO = 1, INVOICEDETAILID = 2, CARETUSED = 100},
            new Mixing {MIXINGNO = 2, INVOICEDETAILID = 1, CARETUSED = 25},
            new Mixing {MIXINGNO = 2, INVOICEDETAILID = 2, CARETUSED = 50}
        };

        var q =
            invoices.Join(invoiceDetails, i => i.INVOICENO, id => id.INVOICENO, (invoice, detail) => new {invoice, detail})
                .GroupJoin(mixings, arg => arg.detail.INVOICEDETAILID, m => m.INVOICEDETAILID,
                    (arg, m) => new {arg.invoice, arg.detail, Mixings = m})
                .GroupBy(arg => arg.invoice)
                .Select(
                    g =>
                        new
                        {
                            g.Key.INVOICENO,
                            g.Key.DATE,
                            Tot_Caret = g.Sum(arg => arg.detail.CARET),
                            Tot_Used = g.Sum(arg => arg.Mixings.Sum(mixing => mixing.CARETUSED)),
                            Available = g.Sum(arg => arg.detail.CARET) - g.Sum(arg => arg.Mixings.Sum(mixing => mixing.CARETUSED))
                        });
    }

【讨论】:

  • 是的,虽然我花了几次迭代
【解决方案4】:

如果有人有 LINQ 的解决方案,请在此处发布。 @Jeroen van Langen 的答案非常接近,但给了我错误。

经过一些修改和尝试,我至少解决了 Raw SQLQuery 的问题。 LINQ 并没有帮助我处理复杂和嵌套的查询。 以下是我非常熟悉的原始 SQL 代码,它是一个临时解决方案。根据我的最终要求,我还添加了一些其他表格。

使用原始 SQL 查询的临时工作解决方案

var str = "select";
        str += " a.ID,a.INVOICENO,a.TOTAL,a.CARET,a.DATE,a.PARTY,a.BROKER,";
        str += " ISNULL(b.CARET,0) as ISSUECARET,";
        str += " ISNULL(a.CARET,0) - ISNULL(b.CARET,0) as AVAILABLECARET";
        str += " from";
        str += " (";
        str += " select";
        str += "     i.INVOICENO as ID,";
        str += " i.INVOICENO,";
        str += " i.DATE,";
        str += " a.accountname as PARTY,";
        str += " b.accountname as BROKER,";
        str += " SUM(id.CARET) as CARET,";
        str += " SUM(id.TOTAL) as TOTAL";
        str += "     from invoice i";
        str += "     inner";
        str += " join Invoice_Details id";
        str += "                 on i.INVOICENO = id.INVOICENO";
        str += " inner join account a on a.ID=i.party inner join account b on b.ID=i.broker";
        str += "     group by";
        str += " i.id,";
        str += " i.INVOICENO,";
        str += " i.DATE,";
        str += " a.accountname,";
        str += " b.accountname";
        str += " )";
        str += " as a";
        str += " left join";
        str += " (";
        str += " select";
        str += "     id.INVOICENO,";
        str += " SUM(m.caret) as CARET";
        str += "     from";
        str += " Invoice_Details id";
        str += "     left";
        str += " join";
        str += " Mixing m";
        str += " on id.ID = m.INVOICEDETAILID";
        str += "     group by id.invoiceno";
        str += " )";
        str += "    as b";
        str += " on a.INVOICENO = b.INVOICENO";
        var query = db.Database.SqlQuery<Invoice_List>(str);

【讨论】:

  • 你检查我的答案了吗?它也使用 LINQ
  • 是但显示The nested query is not supported. Operation1='GroupBy' Operation2='MultiStreamNest'
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-06
  • 2019-07-29
  • 2017-08-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多