Entity framework GroupJoin:引发异常:System.InvalidOperationException

Entity framework GroupJoin:引发异常:System.InvalidOperationException,entity-framework,entity-framework-core,ef-core-2.2,Entity Framework,Entity Framework Core,Ef Core 2.2,我正试图写一个查询,以获取所有餐厅的桌子,如果它存在或没有公开销售。 如果表中存在销售,我希望获得总额和夫妇详细信息。这是我的代码: db.SALETABLES .GroupJoin( db.SALES.Where(c => c.CLOSEDTIME == null), t => t.ID, sa => sa.ID_TABLE, (ta, s) => new {

我正试图写一个查询,以获取所有餐厅的桌子,如果它存在或没有公开销售。 如果表中存在销售,我希望获得总额和夫妇详细信息。这是我的代码:

   db.SALETABLES
  .GroupJoin(
        db.SALES.Where(c => c.CLOSEDTIME == null),
        t => t.ID,
        sa => sa.ID_TABLE,
     (ta, s) => new
            {
               ta.ID,
               ta.DESCRIPTION,
                NR_SALE = s.Any() ? s.First().NR_SALE : 0,
                IDSALE = s.Any() ? s.First().ID : 0,
                IDUSER = s.Any() ? s.First().IDUSER : 0,
                USERNAME = s.Any() ? s.First().USERS.USERNAME :"" ,
                SALESUM = s.Any() ? s.First().SALES_DETAIL.Sum(p => p.PRICE * p.CANT) : 0
                         }
但我犯了这个错误:

引发异常:中的“System.InvalidOperationException” System.Private.CoreLib.dll


感谢您的帮助

您没有指定异常,但我假设它是关于客户端评估CSE的,并且您将EF配置为在异常发生时引发异常

它可能是第一个触发CSE或GroupJoin的。前者可以通过使用FirstOrDefault轻松修复。GroupJoin还有更多的功能

在许多情况下,根本不需要使用GroupJoin,当然也不必使用Join。通常,手动编码的联接可以而且应该由导航属性替换。这不仅提高了代码的可读性,还避免了EF2.x在GroupJoin中遇到的几个问题

你的SaleTable类我不会跟随你的数据库驱动的名字应该有一个物业销售:

配置为

modelBuilder.Entity<SaleTable>()
    .HasMany(e => e.Sales)
    .WithOne(e => e.SaleTable)
    .HasForeignKey(e => e.SaleTableId) // map this to ID_TABLE
    .IsRequired();

使用NrSale=firstSale.NrSale,将为没有Sales Nullable对象的SaleTables引发异常。必须有一个值。

您没有指定异常,但我假设它是关于客户端评估CSE的,并且您已将EF配置为在异常发生时引发异常

它可能是第一个触发CSE或GroupJoin的。前者可以通过使用FirstOrDefault轻松修复。GroupJoin还有更多的功能

在许多情况下,根本不需要使用GroupJoin,当然也不必使用Join。通常,手动编码的联接可以而且应该由导航属性替换。这不仅提高了代码的可读性,还避免了EF2.x在GroupJoin中遇到的几个问题

你的SaleTable类我不会跟随你的数据库驱动的名字应该有一个物业销售:

配置为

modelBuilder.Entity<SaleTable>()
    .HasMany(e => e.Sales)
    .WithOne(e => e.SaleTable)
    .HasForeignKey(e => e.SaleTableId) // map this to ID_TABLE
    .IsRequired();

使用NrSale=firstSale.NrSale,将为不带Sales Nullable对象的SaleTables抛出异常。必须有一个值。

由于异常是由EF Core基础结构引起的,显然您遇到了当前EF Core实现错误

但在编写LINQ to Entities查询时,您可以遵循一些规则,帮助EF Core查询转换器避免因缺少用例而导致的错误。在大多数情况下,这些规则还将消除EF Core 3.0+中查询或异常的客户端评估

作为此特定查询问题根源的规则之一是-决不首先使用。第一个的LINQ to Objects行为是在集合为空时引发异常。这对于SQL来说是不自然的,SQL自然支持并返回NULL,即使对于通常不允许NULL的值也是如此。为了模拟LINQ到对象的行为,EF核心必须评估第一个客户端,即使它可以工作,这也是不好的。相反,使用FirstOrDefault,它与SQL具有相同的语义,因此被翻译

重述一下,当您需要结果为单个对象或null时,请使用FirstOrDefault;当您希望结果为包含0或一个元素的集合时,请使用Take1

在这种特殊情况下,最好将0或1相关销售规则直接合并到联接子查询中,方法是删除GroupJoin并将其替换为SelectMany with correlated Where。所有检查都将替换为!=空检查

话虽如此,修改后的可工作且完全由服务器翻译的查询如下所示:

var query = db.SALETABLES
    .SelectMany(ta => db.SALES
        .Where(s => ta.ID == s.ID_TABLE && s.CLOSEDTIME == null).Take(1), // <--
    (ta, s) => new
    {
        ta.ID,
        ta.DESCRIPTION,
        NR_SALE = s != null ? s.NR_SALE : 0,
        IDSALE = s != null ? s.ID : 0,
        IDUSER = s != null  ? s.IDUSER : 0,
        USERNAME = s != null ? s.USERS.USERNAME : "",
        SALESUM = s != null ? s.SALES_DETAIL.Sum(p => p.PRICE * p.CANT) : 0
    });

由于EF核心基础设施是一个例外,显然您遇到了当前的EF核心实现错误

但在编写LINQ to Entities查询时,您可以遵循一些规则,帮助EF Core查询转换器避免因缺少用例而导致的错误。在大多数情况下,这些规则还将消除EF Core 3.0+中查询或异常的客户端评估

作为此特定查询问题根源的规则之一是-决不首先使用。第一个的LINQ to Objects行为是在集合为空时引发异常。这对于SQL来说是不自然的,SQL自然支持并返回NULL,即使对于通常不允许NULL的值也是如此。为了模拟LINQ到对象的行为,EF核心必须评估第一个客户端,即使它可以工作,这也是不好的。相反,使用FirstOrDefault,它与SQL具有相同的语义,因此被翻译

重述一下,当您需要结果为单个对象或null时,请使用FirstOrDefault;当您希望结果为包含0或一个元素的集合时,请使用Take1

在这种特殊情况下,最好将0或1相关销售规则直接合并到联接子查询中,方法是删除GroupJoin并将其替换为SelectMany with correlated Where。所有检查都将替换为!=空检查

话虽如此,修改后的可工作且完全由服务器翻译的查询如下所示:

var query = db.SALETABLES
    .SelectMany(ta => db.SALES
        .Where(s => ta.ID == s.ID_TABLE && s.CLOSEDTIME == null).Take(1), // <--
    (ta, s) => new
    {
        ta.ID,
        ta.DESCRIPTION,
        NR_SALE = s != null ? s.NR_SALE : 0,
        IDSALE = s != null ? s.ID : 0,
        IDUSER = s != null  ? s.IDUSER : 0,
        USERNAME = s != null ? s.USERS.USERNAME : "",
        SALESUM = s != null ? s.SALES_DETAIL.Sum(p => p.PRICE * p.CANT) : 0
    });

请发布查询中使用的类,以便我们可以

n复制问题。请发布查询中使用的类,以便我们可以复制问题。哇,我们几乎同时发布了您是第一个:-我很好奇您是否尝试了建议的查询-在得到答案之前,我必须使用几个变体。@Ivan Hehe,有趣的是:我确实试过使用我自己的小投资组合中的类似案例,所以我很有信心它会成功。你的分析更深刻,我在第一次使用时没有意识到这个不可避免的异常。虽然在使用它的时候,EF切换到CSE,然后失败了,因为Sequence不包含任何元素。当然,它消除了OP例外,对此我毫不怀疑。不幸的是,我们显示了许多有关Sum表达式的客户端求值警告,并且还为每个计划的firstSale属性生成了单独的子查询。更奇怪的是,如果在st.SALES.Wheres=>s.CLOSEDTIME==null.Take1中将查询的let部分替换为firstSale中的等效项,情况会变得更糟。因此,与通常使用导航属性进行更好的转换不同,这种类型的查询使用手动连接时效果更好。糟糕的EF核心,等待v3:OMG,我想我必须完全复制1:1。我的案例只是警告我在使用FirstOrDefault时,如果不进行排序,可能会出现不可预测的结果,但我很乐意使用SUM语句将其转换为SQL。这就是EF core的问题所在,细微的差异可能会毁掉整件事。我将在这里留下我的答案,因为我认为这种方法总体上可能对OP和未来的读者有所帮助。谢谢这让我想知道。。。我使用了Linqpad,所以它是经典.Net框架中的EF核心。可能与.Net core有所不同?有件事让我想知道……哇,我们几乎同时发布了你是第一个,不过:-我很好奇你是否尝试了建议的查询-在得到我的答案之前,我不得不玩几个变体。@Ivan Hehe,有趣的是:我确实使用了我自己的小投资组合中的类似案例尝试了这个,所以我很有信心它会成功。你的分析更深刻,我在第一次使用时没有意识到这个不可避免的异常。虽然在使用它的时候,EF切换到CSE,然后失败了,因为Sequence不包含任何元素。当然,它消除了OP例外,对此我毫不怀疑。不幸的是,我们显示了许多有关Sum表达式的客户端求值警告,并且还为每个计划的firstSale属性生成了单独的子查询。更奇怪的是,如果在st.SALES.Wheres=>s.CLOSEDTIME==null.Take1中将查询的let部分替换为firstSale中的等效项,情况会变得更糟。因此,与通常使用导航属性进行更好的转换不同,这种类型的查询使用手动连接时效果更好。糟糕的EF核心,等待v3:OMG,我想我必须完全复制1:1。我的案例只是警告我在使用FirstOrDefault时,如果不进行排序,可能会出现不可预测的结果,但我很乐意使用SUM语句将其转换为SQL。这就是EF core的问题所在,细微的差异可能会毁掉整件事。我将在这里留下我的答案,因为我认为这种方法总体上可能对OP和未来的读者有所帮助。谢谢这让我想知道。。。我使用了Linqpad,所以它是经典.Net框架中的EF核心。可能与.Net core有所不同?我想知道一些事情…谢谢你的回答。但是,Take1只会为所有表返回一次销售,而不仅仅是每个表exists@mrapi该死,你当然是对的!然后Take1应该转到DefaultIfEmpty的位置,然后我们回到客户评估。感谢您的捕获,现在应该可以了-转换为交叉应用查询,该查询将获取每个第一个相关记录。在这种情况下,查询速度非常慢,并且不会返回所有表。如果我删除,您使用GroupJoin的第一个版本似乎可以工作。Take1每个表最多可以有一个已打开的销售。谢谢你的回答。但是,Take1只会为所有表返回一次销售,而不仅仅是每个表exists@mrapi该死,你当然是对的!然后Take1应该转到DefaultIfEmpty的位置,然后我们回到客户评估。感谢您的捕获,现在应该可以了-转换为交叉应用查询,该查询将获取每个第一个相关记录。在这种情况下,查询速度非常慢,并且不会返回所有表。如果我删除,您使用GroupJoin的第一个版本似乎可以工作。Take1每个表最多可以有一个已打开的销售。谢谢
var query = db.SALETABLES
    .SelectMany(ta => db.SALES
        .Where(s => ta.ID == s.ID_TABLE && s.CLOSEDTIME == null).Take(1), // <--
    (ta, s) => new
    {
        ta.ID,
        ta.DESCRIPTION,
        NR_SALE = s != null ? s.NR_SALE : 0,
        IDSALE = s != null ? s.ID : 0,
        IDUSER = s != null  ? s.IDUSER : 0,
        USERNAME = s != null ? s.USERS.USERNAME : "",
        SALESUM = s != null ? s.SALES_DETAIL.Sum(p => p.PRICE * p.CANT) : 0
    });