2013-10-02 1 views
2

У меня есть отношения многие ко многим:Как считать все сообщения, принадлежащие нескольким тегам в NHibernate?

  • Пост может иметь много тегов
  • Тег может иметь много сообщений

Модели:

public class Post 
{ 
    public virtual string Title { get; set; } 
    public virtual string Content{ get; set; } 
    public virtual User User { get; set; } 
    public virtual ICollection<Tag> Tags { get; set; } 
} 

public class Tag 
{ 
    public virtual string Title { get; set; } 
    public virtual string Description { get; set; } 
    public virtual User User { get; set; } 
    public virtual ICollection<Post> Posts { get; set; } 
} 

Я хочу сосчитать все сообщения, относящиеся к нескольким тегам, но я не знаю, как это сделать в NHibernate. Я не уверен, если это лучший способ сделать это, но я использовал этот запрос в MS SQL:

SELECT COUNT(*) 
    FROM 
    (
    SELECT Posts.Id FROM Posts 
    INNER JOIN Users ON Posts.UserId=Users.Id 
    LEFT JOIN TagsPosts ON Posts.Id=TagsPosts.PostId 
    LEFT JOIN Tags ON TagsPosts.TagId=Tags.Id 
    WHERE Users.Username='mr.nuub' AND (Tags.Title in ('c#', 'asp.net-mvc')) 
    GROUP BY Posts.Id 
    HAVING COUNT(Posts.Id)=2 
    )t 

Но NHibernate не позволяют подзапросы в ЕКЕ. Было бы здорово, если бы кто-нибудь мог показать мне, как это сделать в HQL.

+0

Почему бы вам просто не избавиться от подзапроса, поскольку вы ничего не делаете с ним? Просто 'COUNT (Posts.Id)' ?? – Charleh

+0

Это возвращает 3 строки: '2', '2', '2'.Теперь я рассчитываю результаты подзапроса. –

ответ

1

Я нашел способ, как получить этот результат без подзапроса и это работает с NHibernate Linq. Это было на самом деле не так просто из-за подмножества Linq выражений, которые поддерживаются NHibernate ... но в любом случае

запрос:

var searchTags = new[] { "C#", "C++" }; 
var result = session.Query<Post>() 
     .Select(p => new { 
      Id = p.Id, 
      Count = p.Tags.Where(t => searchTags.Contains(t.Title)).Count() 
     }) 
     .Where(s => s.Count >= 2) 
     .Count(); 

Он производит следующие SQL Постулаты:

select cast(count(*) as INT) as col_0_0_ 
from Posts post0_ 
where (
    select cast(count(*) as INT) 
    from PostsToTags tags1_, Tags tag2_ 
    where post0_.Id=tags1_.Post_id 
    and tags1_.Tag_id=tag2_.Id 
    and (tag2_.Title='C#' or tag2_.Title='C++'))>=2 

Надеюсь, вы должны уметь ограничить свое пользовательское ограничение.

Следующая моя испытательная установка и случайные данные, которые получили генерироваться

public class Post 
{ 
    public Post() 
    { 
     Tags = new List<Tag>(); 
    } 
    public virtual void AddTag(Tag tag) 
    { 
     this.Tags.Add(tag); 
     tag.Posts.Add(this); 
    } 
    public virtual string Title { get; set; } 
    public virtual string Content { get; set; } 
    public virtual ICollection<Tag> Tags { get; set; } 
    public virtual int Id { get; set; } 
} 

public class PostMap : ClassMap<Post> 
{ 
    public PostMap() 
    { 
     Table("Posts"); 

     Id(p => p.Id).GeneratedBy.Native(); 

     Map(p => p.Content); 

     Map(p => p.Title); 

     HasManyToMany<Tag>(map => map.Tags).Cascade.All(); 
    } 
} 

public class Tag 
{ 
    public Tag() 
    { 
     Posts = new List<Post>(); 
    } 
    public virtual string Title { get; set; } 
    public virtual string Description { get; set; } 
    public virtual ICollection<Post> Posts { get; set; } 
    public virtual int Id { get; set; } 
} 

public class TagMap : ClassMap<Tag> 
{ 
    public TagMap() 
    { 
     Table("Tags"); 
     Id(p => p.Id).GeneratedBy.Native(); 

     Map(p => p.Description); 
     Map(p => p.Title); 
     HasManyToMany<Post>(map => map.Posts).LazyLoad().Inverse(); 
    } 
} 

испытательный пробег:

var sessionFactory = Fluently.Configure() 
    .Database(FluentNHibernate.Cfg.Db.MsSqlConfiguration.MsSql2012 
     .ConnectionString(@"Server=.\SQLExpress;Database=TestDB;Trusted_Connection=True;") 
     .ShowSql) 
    .Mappings(m => m.FluentMappings 
     .AddFromAssemblyOf<PostMap>()) 
    .ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(false, true)) 
    .BuildSessionFactory(); 

using (var session = sessionFactory.OpenSession()) 
{ 
    var t1 = new Tag() { Title = "C#", Description = "C#" }; 
    session.Save(t1); 
    var t2 = new Tag() { Title = "C++", Description = "C/C++" }; 
    session.Save(t2); 
    var t3 = new Tag() { Title = ".Net", Description = "Net" }; 
    session.Save(t3); 
    var t4 = new Tag() { Title = "Java", Description = "Java" }; 
    session.Save(t4); 
    var t5 = new Tag() { Title = "lol", Description = "lol" }; 
    session.Save(t5); 
    var t6 = new Tag() { Title = "rofl", Description = "rofl" }; 
    session.Save(t6); 
    var tags = session.Query<Tag>().ToList(); 
    var r = new Random(); 

    for (int i = 0; i < 1000; i++) 
    { 
     var post = new Post() 
     { 
      Title = "Title" + i, 
      Content = "Something awesome" + i, 
     }; 

     var manyTags = r.Next(1, 3); 

     while (post.Tags.Count() < manyTags) 
     { 
      var index = r.Next(0, 6); 
      if (!post.Tags.Contains(tags[index])) 
      { 
       post.AddTag(tags[index]); 
      } 
     } 

     session.Save(post); 
    } 
    session.Flush(); 

    /* query test */ 
    var searchTags = new[] { "C#", "C++" }; 
    var result = session.Query<Post>() 
      .Select(p => new { 
       Id = p.Id, 
       Count = p.Tags.Where(t => searchTags.Contains(t.Title)).Count() 
      }) 
      .Where(s => s.Count >= 2) 
      .Count(); 

    var resultOriginal = session.CreateQuery(@" 
     SELECT COUNT(*) 
     FROM 
     (
     SELECT count(Posts.Id)P FROM Posts 
     LEFT JOIN PostsToTags ON Posts.Id=PostsToTags.Post_id 
     LEFT JOIN Tags ON PostsToTags.Tag_id=Tags.Id 
     WHERE Tags.Title in ('c#', 'C++') 
     GROUP BY Posts.Id 
     HAVING COUNT(Posts.Id)>=2 
     )t 
    ").List()[0]; 

    var isEqual = result == (int)resultOriginal; 
} 

Как вы можете видеть, в конце я делаю тест с первоначальным запросом (без пользователей), и это на самом деле то же самое количество.

+0

Большое спасибо, это отлично работает :) Не могли бы вы также привести пример в HQL? –

+0

Мой ответ показывает, как получить результат с помощью HQL. –

+0

На самом деле это хорошая идея не использовать HQL, потому что строго типизировано> все ... И ни один из ваших опубликованных hql не работает вообще – MichaC

0

Редактировать: В будущем я должен прочитать вопросы до последних слов. Я видел бы in HQL ...


Через некоторое поиске делового, понимая, что RowCount удаляет любую группировку в запросе (https://stackoverflow.com/a/8034921/1236044). Я нашел решение, используя QueryOver и SubQuery, которые я размещаю здесь как информацию. Я нахожу это решение интересным, так как он предлагает некоторую модульность и разделяет подсчет от самого подзапроса, который можно использовать повторно.

var searchTags = new[] { "tag1", "tag3" }; 
var userNames = new[] { "mr.nuub" }; 
Tag tagAlias = null; 
Post postAlias = null; 
User userAlias = null; 

var postsSubquery = 
    QueryOver.Of<Post>(() => postAlias) 
      .JoinAlias(() => postAlias.Tags,() => tagAlias) 
      .JoinAlias(() => postAlias.User,() => userAlias) 
      .WhereRestrictionOn(() => tagAlias.Title).IsIn(searchTags) 
      .AndRestrictionOn(() => userAlias.UserName).IsIn(userNames) 
      .Where(Restrictions.Gt(Projections.Count<Post>(p => tagAlias.Title), 1)); 

var numberOfPosts = session.QueryOver<Post>() 
      .WithSubquery.WhereProperty(p => p.Id).In(postsSubquery.Select(Projections.Group<Post>(p => p.Id))) 
      .RowCount(); 

Надеется, что это поможет

+0

спасибо, но это не работает, к сожалению, –

+0

@ Mr.nuub создает запрос или он сбой перед этим? Отредактировав мой ответ, добавив финальную проекцию – jbl

+0

Он обрушился на эту строку: .AndRestrictionOn (p => p.User.UserName) .IsIn (userNames), но у меня есть только один пользователь, поэтому я могу оставить его на данный момент. Результатом этого запроса является 5 для тегов 'C#' и 'asp.net-mvc'. Но это должно быть 2. –

0

В HQL:

var hql = "select count(p) from Post p where p in " + 
    "(select t.Post from Tag t group by t.Post having count(t.Post) > 1)"; 
var result = session.Query(hql).UniqueResult<long>(); 

Вы можете добавить дополнительные критерии подзапрос, если вам нужно указать теги или другие критерии.

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