将 SQL 语句重写为 LINQ 查询会导致运行时错误

时间:2021-05-10 11:13:18

标签: c# sql linq .net-core entity-framework-core

作为 this question of mine 的后续,我得到了这个 SQL:

SELECT Documents.* 
FROM Documents 
WHERE Documents.ID IN
(
  SELECT Keywords.DocumentID 
  FROM Keywords
  WHERE 
    Keywords.Keyword = 'KeywordA' OR 
    Keywords.Keyword = 'KeywordB' 
  GROUP BY Keywords.DocumentID 
  HAVING COUNT(Keywords.Keyword) = 2 
)

我确实使用 Linqer 将查询转换为 C# 以与 Entity Framework Core 5 一起使用:

from Document in db.Document
where
    (from Keyword in db.Keyword
    where
      Keyword.Value == "KeywordA" ||
      Keyword.Value == "KeywordB"
    group Keyword by new {
      Keyword.DocumentId
    } into g
    where g.Count(p => p.Value != null) == 2
    select new {
      g.Key.DocumentId
    }).Contains(new { DocumentId = Document.DocumentId })
select Document

这编译成功,但在运行查询时,我收到一个错误:

<块引用>

无法翻译 LINQ 表达式“<see below>”。以可翻译的形式重写查询,或通过插入对“AsEnumerable”、“AsAsyncEnumerable”、“ToList”或“ToListAsync”的调用,显式切换到客户端评估。有关详细信息,请参阅 https://go.microsoft.com/fwlink/?linkid=2101038

上述错误消息中格式化的 LINQ 表达式如下:

DbSet<Keyword>()
    .Where(k => k.Value == "KeywordA" || k.Value == "KeywordB")
    .GroupBy(
        keySelector: k => new { DocumentId = k.DocumentId }, 
        elementSelector: k => k)
    .Where(e => e
        .Count(p => p.Value != null) == 2)
    .Select(e => new { DocumentId = e.Key.DocumentId })
    .Any(p => p == new { DocumentId = EntityShaperExpression: 
        EntityType: Document
        ValueBufferExpression: 
            ProjectionBindingExpression: EmptyProjectionMember
        IsNullable: False
    .DocumentId })

我真的不明白这里有什么问题。我只能想象 Linqer 太老了,无法生成用于 EF Core 5 的有效 C# 代码。

我的问题

有人能告诉我我在这里做错了什么以及如何解决问题吗? (即如何重写 C# 查询)

4 个答案:

答案 0 :(得分:2)

看起来 .Contains(new { DocumentId = Document.DocumentId }) 部分是您的问题。将其转换为表达式时遇到问题,因为此时 Document 尚未被评估。

如果你有 FK 设置,你可以这样重构它:

from d in db.Documents
where d.Keywords
       .Where(k => (new[] { "keyword A", "keyword B" }).Contains(k.Keyword))
       .Count() == 2
select d

答案 1 :(得分:2)

EF Core 查询翻译仍然不支持许多 LINQ 结构,所有这些没有确切支持/不支持什么的文档确实让我们走上了反复试验的道路,因此外部工具无法生成也就不足为奇了“正确”的翻译。

在这种特殊情况下,问题是带有复杂参数的 Contains 调用(尽管它是一个具有单个成员的类)。因为它们只支持带有原始参数的 Contains

因此,使这项工作所需的最小更改是替换

select new
{
    g.Key.DocumentId
}).Contains(new { DocumentId = Document.DocumentId })

select g.Key.DocumentId).Contains(Document.DocumentId)

答案 2 :(得分:1)

试试这个:

var result =
    from Document in db.Document
    where
        (from Keyword in db.Keyword
         where
       Keyword.Value == "KeywordA" ||
       Keyword.Value == "KeywordB"
         group Keyword by Keyword.DocumentId
          into g
         where g.Count(p => p.Value != null) == 2
         select g.Key).Any(d => d == Document.DocumentId)
    select Document;

答案 3 :(得分:1)

如果有 2 个单独的查询,1 个用于关键字,1 个用于文档,这不是问题,这也应该有效:

var matchedDocumentIds = db.Keywords
    .Where(keyword => keyword.Keyword == "KeywordA" || keyword.Keyword == "KeywordB")
    .GroupBy(keyword => keyword.DocumentID)
    .Where(grp => grp.Count() > 2)
    .Select(grp => grp.Key);

var filteredDocs = db.Documents.Where(doc => matchedDocumentIds.Any(matchedDocId => matchedDocId == doc.ID));

这假设两个表之间有一个外键。

相关问题