【问题标题】:LINQ - Searching one to many for exact match of arrayLINQ - 一对多搜索数组的精确匹配
【发布时间】: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


【解决方案1】:

我相信All 就是您在这里寻找的东西

应该是这样的

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.All(sc => characters.Contains(sc.CharacterIdINavigation.NameVc))
        select s;

【讨论】:

  • 我使用了您的 LINQ 查询,但还需要添加它才能使用正确的 SQL:where s.TblStoryCharacter.Count == 2。
【解决方案2】:

试试这样的:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;


namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            string[] names = {"Harry", "Tom"};

            var results = (from sc in Story_Character.story_character
                           join chr in Character.character on sc.Id_i equals chr.Id_i
                           join st in Story.story on sc.Id_i equals st.Id_i
                           join auth in Author.author on sc.Id_i equals auth.Id_i
                           where names.Contains(chr.Name_vc)
                           select new { sc = sc, chr = chr, st = st, auth = auth })
                          .GroupBy(x => x.sc.StoryId_i).Where(x => x.Count() >= 2).ToList();

        }
    }
    public class Story_Character
    {
        public static List<Story_Character> story_character = new List<Story_Character>();
        public int Id_i { get; set; }
        public int StoryId_i { get; set; }
        public int CharacterId_i { get; set; }
    }

    public class Character
    {
        public static List<Character> character = new List<Character>();
        public int Id_i { get; set; }
        public string Name_vc { get; set; }
    }
    public class Story
    {
        public static List<Story> story = new List<Story>();
        public int Id_i { get; set; }
        public DateTime Published_dt { get; set; }
        public string Title_vc { get; set; }
        public string AuthorId_i { get; set; }
    }
    public class Author
    {
        public static List<Author> author = new List<Author>();
        public int Id_i { get; set; }
        public string Name_vc { get; set; }
        public string Url_vc { get; set; }
    }

}

【讨论】:

  • 这将正确识别以 Harry 和 Tom 作为角色的故事,但我认为也将包括以 Harry、Tom 和其他随机角色为角色的故事。对吗?
  • 每个 Id_i 只有一个字符名,因为 Id_i 是主键。所以不可能有任何其他的字符名称。现在有可能这两个 id_i 具有相同的字符名称。我认为没有必要测试 x.Count() >= 2。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-08-02
  • 1970-01-01
  • 2013-09-15
  • 1970-01-01
  • 1970-01-01
  • 2018-08-23
  • 2022-01-19
相关资源
最近更新 更多