【问题标题】:LINQ many-to-many relationship, how to write a correct WHERE clause?LINQ多对多关系,如何写出正确的WHERE子句?
【发布时间】:2012-05-08 19:58:10
【问题描述】:

我的表使用多对多关系。

有一个查询:

var query = from post in context.Posts
        from tag in post.Tags where tag.TagId == 10
        select post;

好的,它工作正常。我收到带有 id 指定标签的帖子。

我有一组标签 ID。而且我想获得包含我收藏中每个标签的帖子。

我尝试以下方式:

var tagIds = new int[]{1, 3, 7, 23, 56};

var query = from post in context.Posts
        from tag in post.Tags where tagIds.Contains( tag.TagId )
        select post;

它不起作用。该查询返回具有任何一个指定标签的所有帖子。

我想得到一个这样的子句,但动态地用于集合中任何数量的标签:

post.Tags.Whare(x => x.TagId = 1 && x.TagId = 3 && x.TagId = 7 && ... )

【问题讨论】:

标签: c# linq entity-framework-4 dbcontext


【解决方案1】:

您不应该在 outer 查询中投影每个帖子的标签;相反,您需要使用内部查询来执行外部过滤器的检查。 (在 SQL 中,我们习惯称之为correlated subquery。)

var query = 
    from post in context.Posts
    where post.Tags.All(tag => tagIds.Contains(tag.TagId))
    select post;

替代语法:

var query = 
    context.Posts.Where(post =>
        post.Tags.All(tag => 
            tagIds.Contains(tag.TagId)));

编辑:根据Slauma’s clarification 进行更正。以下版本返回的帖子至少包含 tagIds 集合中的所有标签。

var query = 
    from post in context.Posts
    where tagIds.All(requiredId => post.Tags.Any(tag => tag.TagId == requiredId))
    select post;

替代语法:

var query = 
    context.Posts.Where(post => 
        tagIds.All(requiredId => 
            post.Tags.Any(tag =>
                tag.TagId == requiredId)));

编辑2:根据 Slauma 进行了上述更正。还包括另一个充分利用以下查询语法的替代方案:

// Project posts from context for which
// no Ids from tagIds are not matched
// by any tags from post
var query =
    from post in context.Posts
    where
    ( 
        // Project Ids from tagIds that are
        // not matched by any tags from post
        from requiredId in tagIds
        where
        (
            // Project tags from post that match requiredId
            from tag in post.Tags
            where tag.TagId == requiredId
            select tag
        ).Any() == false
        select requiredId 
    ).Any() == false
    select post;

我在 Transact-SQL 中使用.Any() == false 模拟了NOT EXISTS 运算符。

【讨论】:

  • +1 是唯一一个只匹配 ALL tagId,而不仅仅是 ANY
  • @mattytommo 你确定吗?在我看来,他正在获得所有职位。编辑:他刚刚添加了 post.Tags.All 块,这应该可以解决问题,但 Tejs 先有这个。
  • 没错,我只是假设你知道一些我不知道的事情。即便如此,mattytommo 仍然不正确,因为您最初的想法(如果它有效)不会导致只返回带有所有标签的帖子。 +1,因为您更新的答案具有我更喜欢的更长的 linq 和替代并且写得很好。
  • 嗯,你不能说他是否正确,因为这取决于假设的Contains(IEnumerable<T>) 在逻辑上是否等同于AnyAll。我假设它等同于All(如“包含子集”)。
  • 啊,Any 看起来更好!最后一个代码 sn -p 看起来很奇怪。对我来说,这是一个很好的例子,一旦查询变得更复杂,扩展方法语法就更容易阅读。我显然更喜欢带有“缩进级联”的版本(但缩进不同):)
【解决方案2】:

这其实很容易做到:

var tags = context.Posts.Where(post => post.Tags.All(tag => tagIds.Contains(tag)));

【讨论】:

  • 他想要匹配 ALL,而不仅仅是 1
  • 更新后只需使用All 扩展名。
  • 这将返回 all 标签出现在 tagIds 列表中的帖子。例如:tagIds = ("C#","Java") 和只有标签“C#”的帖子。您的查询将返回此帖子。但他想要:“在我的收藏中获得具有每个标签的帖子”。即:仅标记为“C#”“Java”的帖子。也许,不是那么容易做到的? :)
  • @Slauma:难度相同(或“相当容易”)——您只需交换调用AllContains 的集合。我留下了both versions 进行比较。
【解决方案3】:

如果您希望标签集合仅包含您指定的集合而不包含其他集合,则另一种选择是将两个列表相交:

var query = from post in context.Posts
  let tags = post.Tags.Select(x => x.Id).ToList()
  where tags.Intersect(tagIds).Count() == tags.Length
  select post;

【讨论】:

  • 你不想tags.Intersect(tagIds).Count() == tagIds.Length 吗?
  • 我不确定let 内部的ToList() 是否适用于LINQ to Entities。 (我希望“无法转换为商店表达式”异常,tags 也不再是 IQueryable。)在我看来,没有 ToList() 很好。
【解决方案4】:

试试Any

var query = from post in context.Posts
    from tag in post.Tags where tagIds.Any(t => t == tag.TagId )
    select post;

【讨论】:

  • 他想要匹配 ALL,而不仅仅是 1
猜你喜欢
  • 2016-12-31
  • 2011-09-15
  • 1970-01-01
  • 2017-01-20
  • 1970-01-01
  • 2022-12-07
  • 2010-09-11
  • 2010-10-10
  • 1970-01-01
相关资源
最近更新 更多