2016-01-15 2 views
4

Учитывая простой механизм блога, в котором есть сообщения и теги, связанные с сообщениями. В базе данных: 2 таблицы: Post и Tag, также PostTag таблица для отношений «многие ко многим».Найти строки, в которых дочерняя коллекция содержит все элементы списка

У меня есть список тегов, и я хочу, чтобы найти все сообщения, которые имеют все эти теги (так .IsIn() не работает здесь)

Вопрос: как я могу добиться этого с помощью NHibernate? (в идеале с использованием метода .QueryOver<>())

Проблема в том, что я даже не знаю с чего начать и как ее реализовать в чистом SQL. У меня есть 2 идеи:

  1. Получить все сообщения, а затем фильтровать их с помощью LINQ (т.е. с .IsSupersetOf() функции)
  2. При использовании SQL WHERE EXISTS для каждого элемента в списке

Но я верю, что есть более элегантный способ

Таблицы структура

CREATE TABLE Post (
     Id INT PRIMARY KEY, 
     Title NVARCHAR(255) NOT NULL 
    ); 

    CREATE TABLE Tag (
     Id INT PRIMARY KEY, 
     Tag NVARCHAR(50) NOT NULL 
    ); 

    CREATE TABLE PostTag (
     PostId INT NOT NULL REFERENCES Post(Id), 
     TagId INT NOT NULL REFERENCES Tag(Id) 
    ); 

    INSERT INTO Post(Id, Title) VALUES (1, 'Post A'); 
    INSERT INTO Post(Id, Title) VALUES (2, 'Post B'); 
    INSERT INTO Post(Id, Title) VALUES (3, 'Post C'); 

    INSERT INTO Tag(Id, Tag) VALUES (1, 'tagA'); 
    INSERT INTO Tag(Id, Tag) VALUES (2, 'tagB'); 

    INSERT INTO PostTag (PostId, TagId) VALUES (1, 1); 
    INSERT INTO PostTag (PostId, TagId) VALUES (2, 2); 
    INSERT INTO PostTag (PostId, TagId) VALUES (3, 1); 
    INSERT INTO PostTag (PostId, TagId) VALUES (3, 2); 

И я хочу, чтобы получить сообщение с идентификатором 3 по заданному списку тегов ID: (1, 2)

ответ

2

Использование Queryover решение должно выглядеть,

Tag tagAlias = new Tag(); 
Post postAlias = new Post(); 

Tag tagAliasInner = new Tag(); 
Post postAliasInner = new Post(); 

var subQuery = QueryOver.Of(() => postAliasInner) 
    .JoinAlias(() => postAliasInner.Tags,() => tagAliasInner) 
    .Where(Restrictions.EqProperty(Projections.Property(() => postAliasInner.Id), 
     Projections.Property(() => postAlias.Id))) 
    .Where(Restrictions.In(Projections.Property(() => tagAliasInner.Id), ids.ToArray())) 
    .Select(Projections.Count(Projections.Property(() => tagAliasInner.Id))); 

var query = session.QueryOver(() => postAlias) 
    .JoinAlias(() => postAlias.Tags,() => tagAlias) 
    .Where(Restrictions.In(Projections.Property(() => tagAlias.Id), ids.ToArray())) 
    .WithSubquery.WhereValue(ids.Count).Eq<Post>(subQuery); 

var results = query.List(); 

Это приводит к SQL,

SELECT this_.Id as Id3_1_, 
this_.Title as Title3_1_, 
tags3_.Post_id as Post1_, 
tagalias1_.Id as Tag2_, 
tagalias1_.Id as Id5_0_, 
tagalias1_.Text as Text5_0_ 
FROM "Post" this_ 
inner join PostTag tags3_ on this_.Id=tags3_.Post_id 
inner join "Tag" tagalias1_ on tags3_.Tag_id=tagalias1_.Id 
WHERE tagalias1_.Id in (?, ?) 
and ? = (SELECT count(tagaliasin1_.Id) as y0_ 
      FROM "Post" this_0_ 
      inner join PostTag tags3_ on this_0_.Id=tags3_.Post_id 
      inner join "Tag" tagaliasin1_ on tags3_.Tag_id=tagaliasin1_.Id 
      WHERE this_0_.Id = this_.Id and tagaliasin1_.Id in (?, ?)) 
2

LINQ решение (NHibernate должен быть в состоянии перевести его)

var tags = new[] { 1 , 2 }; 

var postIds = PostTags 
    .Where(pt => tags.Contains(pt.TagId)) 
    .GroupBy(pt => pt.PostId) 
    .Where(g => g.Count() == tags.Length) 
    .Select(g => g.Key); 

SQL Solution :

SELECT PostId 
FROM (
    SELECT COUNT(*) AS count, PostId 
    FROM [PostTag] 
    WHERE TagId IN (1, 2) --List of tags 
    GROUP BY PostId 
    ) as t1 
WHERE [t1].[count] = 2 --Length of list 

Пояснение: мы фильтруем PostTag включать только тег, мы заботимся о. Затем мы группируем по почте. Если подсчет группировки равен длине списка тегов, то сообщение содержит все теги.

+0

Я ошибаюсь, когда я говорю .Contains не будет работать? –

+0

@TomB. Это определенно работает, но после определенного предела (я считаю, ~ 10 000) обычно лучше вставлять в таблицу temp. Но да, генерация SQL работает как для EF, так и для linq2sql. Решение SQL - это унаследованная версия того, что linq2sql генерирует из LINQ. Я бы предположил, что nhibernate также поддерживает его или, по крайней мере, версию. Это очень важно для запроса данных. – Rob

Смежные вопросы