【发布时间】:2017-08-13 06:07:10
【问题描述】:
让我先说这是自学,我正在尝试自学 LINQ 和实体框架。我花了几天时间试图将这个问题底部的 SQL 语句转换为 LINQ,但结果很糟糕。我还在底部添加了 SQL 图。
我的目标是选择与传入的字符串数组具有相同字符的所有故事。我不希望返回包含额外字符或缺少字符的故事。到目前为止,这是我微弱的 LINQ 技能得出的结论:
var characters = new string[] { "Harry", "Tom" };
var cq = _context.TblCharacter.AsNoTracking().Where(c => characters.Contains(c.NameVc));
var q = from c in cq
join sc in _context.TblStoryCharacter.AsNoTracking()
on c.IdI equals sc.CharacterIdI
join s in _context.TblStory.AsNoTracking().Include(s => s.TblStoryCharacter).ThenInclude(sc => sc.CharacterIdINavigation)
on sc.StoryIdI equals s.IdI
where s.TblStoryCharacter.Count() == characters.Length
where s.TblStoryCharacter.Where(sc => characters.Contains(sc.CharacterIdINavigation.NameVc)).Count() == characters.Length
select s;
上面的代码产生了一堆查询(下面的 SQL 分析器图像)并将一堆对象加载到内存中。这种场景有什么 LINQ 魔法吗?
LINQ 衍生查询:
SELECT [t0].[StoryId_i]
FROM [tbl_story_character] AS [t0]
SELECT [sc1].[StoryId_i]
FROM [tbl_story_character] AS [sc1]
INNER JOIN [tbl_character] AS [sc.CharacterIdINavigation0] ON [sc1].[CharacterId_i] = [sc.CharacterIdINavigation0].[Id_i]
WHERE [sc.CharacterIdINavigation0].[Name_vc] IN ('Harry', 'Tom')
这是我开始尝试转换为 LINQ 的 SQL:
select *
from tbl_story
where Id_i in (
select sc.StoryId_i
from tbl_story_character sc
inner join tbl_character c
on c.Id_i = sc.CharacterId_i
where c.Name_vc in ('Harry', 'Tom')
and not exists (
select *
from tbl_story_character subsc
inner join tbl_character subc
on subc.Id_i = subsc.CharacterId_i
where subc.Name_vc not in ('Harry', 'Tom')
and subsc.StoryId_i = sc.StoryId_i
)
group by sc.StoryId_i
having count(*) = 2
)
编辑: 模型是由 EFCore 基于现有数据库生成的,每个模型都包含基于图中外键的导航属性。
听取 Jon Skeet 和 Munzer 的建议后的新 LINQ。
from s in _context.TblStory.AsNoTracking()
.Include(s => s.AuthorIdINavigation)
.Include(s => s.TblStoryCharacter)
.ThenInclude(sc => sc.CharacterIdINavigation)
where s.TblStoryCharacter.All(sc => characters.Contains(sc.CharacterIdINavigation.NameVc))
where s.TblStoryCharacter.Count == 2
select s;
这会产生以下看起来正确的 SQL。
SELECT [s].[Id_i], [s].[AuthorId_i], [s].[Published_dt]
FROM [tbl_story] AS [s]
INNER JOIN [tbl_author] AS [t2] ON [s].[AuthorId_i] = [t2].[Id_i]
WHERE NOT EXISTS (
SELECT 1
FROM [tbl_story_character] AS [sc]
INNER JOIN [tbl_character] AS [sc.CharacterIdINavigation] ON [sc].[CharacterId_i] = [sc.CharacterIdINavigation].[Id_i]
WHERE ([s].[Id_i] = [sc].[StoryId_i]) AND [sc.CharacterIdINavigation].[Name_vc] NOT IN ('Harry', 'Tom')) AND ((
SELECT COUNT(*)
FROM [tbl_story_character] AS [t]
WHERE [s].[Id_i] = [t].[StoryId_i]
) = 2)
ORDER BY [s].[Id_i]
【问题讨论】:
-
我将首先尝试将查询简化为仍然显示问题的较短示例。你能用 一个 加入和一个过滤器重现这个问题吗?基本上,找出它开始爆炸的地方。
-
查看“LINQ 生成的查询”,我会说您使用的是 EF Core,这绝对不是当前(复杂)查询的好工具。但是您的 LINQ 查询看起来很奇怪,其中包含手动连接、导航属性、跟踪急切加载相关结构的所有混合。您已经展示了数据库图,但是对于 EF 查询,拥有所涉及表的实体模型(类)更为重要,您能否发布它?
-
EF6 在查询方面肯定更好。必须尝试Core 2.0,他们说他们在这方面改进了很多,但在我自己尝试之前我不能说这是否正确。
-
@IvanStoev 感谢您的知识
-
不客气。还有一件事 - 使用 LINQ 之类的 SQL(就像当前的两个回答者所做的那样)和 EF 只是浪费时间,并且不会受益于最美丽的 EF 功能之一 - 导航属性。见Don’t Use LINQ’s Join. Navigate!。干杯。
标签: c# .net entity-framework linq .net-core