2013-11-07 4 views
2

Я создаю веб-сайт в ASP.NET MVC и использую NHibernate как ORM. У меня есть следующие таблицы в моей базе данных:Будет ли запрос NHibernate влиять на производительность?

  • Закладки
  • TagsBookmarks (узловая таблица)
  • Метки

Mapping:

public BookmarkMap() 
    { 
     Table("Bookmarks"); 
     Id(x => x.Id).Column("Id").GeneratedBy.Identity(); 
     Map(x => x.Title); 
     Map(x => x.Link); 
     Map(x => x.DateCreated); 
     Map(x => x.DateModified); 
     References(x => x.User, "UserId"); 
     HasManyToMany(x => x.Tags).AsSet().Cascade.None().Table("TagsBookmarks").ParentKeyColumn("BookmarkId") 
     .ChildKeyColumn("TagId"); 
    } 

    public TagMap() 
    { 
     Table("Tags"); 
     Id(x => x.Id).Column("Id").GeneratedBy.Identity(); 
     Map(x => x.Title); 
     Map(x => x.Description); 
     Map(x => x.DateCreated); 
     Map(x => x.DateModified); 
     References(x => x.User, "UserId"); 
     HasManyToMany(x => x.Bookmarks).AsSet().Cascade.None().Inverse().Table("TagsBookmarks").ParentKeyColumn("TagId") 
     .ChildKeyColumn("BookmarkId"); 
    } 

Мне нужна данные из Bookmar ks и таблицы тегов. Более конкретно: мне нужно 20 закладок со связанными тегами. Первое, что я делаю, - выбрать 20 идентификаторов закладок из таблицы Закладок. Я делаю это, потому что пейджинг не работает хорошо на декартовом продукте, который я получаю во втором запросе.

Первый запрос:

IEnumerable<int> bookmarkIds = (from b in SessionFactory.GetCurrentSession().Query<Bookmark>() 
           where b.User.Username == username 
           orderby b.DateCreated descending 
           select b.Id).Skip((page - 1) * pageSize).Take(pageSize).ToList<int>(); 

После этого я выбираю закладки для этих идентификаторов.

Второй запрос:

IEnumerable<Bookmark> bookmarks = (from b in SessionFactory.GetCurrentSession().Query<Bookmark>().Fetch(t => t.Tags) 
            where b.User.Username == username && bookmarkIds.Contains(b.Id) 
            orderby b.DateCreated descending 
            select b); 

Причина я использую выборку, потому что я хочу, чтобы избежать N + 1 запросов. Это работает, но приводит к декартовому продукту. Я читал в некоторых сообщениях, что вам следует избегать декартовых продуктов, но я действительно не знаю, как это сделать в моем случае.

Я также кое-что прочитал о настройке размера партии для запросов N + 1. Это действительно быстрее, чем этот единственный запрос?

Пользователь может добавить max 5 тегов к закладке. Я выбираю 20 закладок на страницу, поэтому наихудший сценарий для этого второго запроса: 5 * 20 = 100 строк.

Будет ли это влиять на производительность, когда у меня есть много данных в таблицах Закладки и Тэги? Должен ли я делать это по-другому?

+0

Каков конечный результат, которого вы пытаетесь достичь? – Fran

+0

Страница с закладками и связанными с ними тегами. Добавлен вопрос. –

ответ

1

Это не декартово произведение.

~ Рисунок A ~

Bookmarks -> Tags -> Tag 

декартово произведение является все возможные комбинации двух различных наборов. Например, предположим, что у нас есть три таблицы: Customer, CustomerAddress и CustomerEmail. У клиентов много адресов, и у них также много адресов электронной почты.

~ Рисунок B ~

Customers -> Addresses 
      -> Emails 

Если вы написали запрос как ...

select * 
from 
    Customer c 
    left outer join CustomerAddress a 
     on c.Id = a.Customer_id 
    left outer join CustomerEmail e 
     on c.Id = e.Customer_id 
where c.Id = 12345 

... и этот клиент имел 5 адресов и 5 адресов электронной почты , вы получите завершенные строки: 5 * 5 = 25. Вы можете понять, почему это было бы плохо для производительности. Это ненужные данные. Знание каждой возможной комбинации адреса и адреса электронной почты для клиента не говорит нам ничего полезного.

С вашим запросом вы не возвращаете лишние строки. Каждая строка в наборе результатов соответствует строке в одной из интересующих вас таблиц и наоборот. Нет умножения. Вместо этого у вас есть TagsBookmarksCount + BookmarksThatDontHaveTagsCount.

Ключевое место для поиска декартовых товаров - это когда ваш запрос разветвляется на две отдельные несвязанные коллекции. Если вы просто копаете все глубже и глубже в одну цепочку детских коллекций, как в Рисунок A, нет декартова произведения. Количество строк, возвращаемых вашим запросом, будет ограничено количеством строк, возвращаемых этой самой глубокой коллекцией. Как только вы отходите в сторону, чтобы теперь у вас были две параллельные, бок о бок коллекции в запросе, как в Рисунок B, то у вас есть декартово произведение, и результаты будут излишне умножены.

Чтобы исправить декартово произведение, разделите запрос на несколько запросов, чтобы количество строк было добавлено, а не умножено. С помощью методов NHibernate Future вы можете объединить эти отдельные запросы вместе, так что у вас есть только одна обратная связь с базой данных. См. one of my other answers для примера того, как исправить декартово произведение в NHibernate.

+0

Итак, если я правильно вас понимаю, у меня нет декартова продукта? Если да, может ли ссылка, которую вы предоставили, помочь в ее устранении? Большое спасибо за ваш ответ! –

+0

Исправить. У вас нет декартова продукта. В какой-то другой момент в будущем, если у вас есть декартовский продукт, с которым вам нужно иметь дело, эта ссылка дает пример того, как его исправить. –

+0

Oke большой. У меня есть один последний вопрос :) Я, вероятно, слишком обеспокоен, но будет ли мой запрос работать хорошо или это должно быть сделано по-другому? –

0

Query<>.Fetch() предназначен для того, чтобы жадная загрузка происходит, и когда это отношения один-ко-многим, так как это, как представляется, (то есть, если Bookmark.Tags этого собрания), то два пути вы собираетесь об этом примерно эквивалентны. Если Tags имеет ленивую загрузку и доступ к ней редко, то отказ от него не может быть лучшим способом (как в вашем первом запросе), потому что вы не всегда будете получать доступ к тегам. Это зависит от варианта использования.

Если, с другой стороны, вы знаете, что вы всегда будете получать все теги, это может иметь смысл разбить это на другой запрос, на этот раз на то, что тип/таблица Tags, и посмотреть вместо того, чтобы использовать отношения NHibernate для выполнения этой работы.

Если Tag имеет внешний ключ к Bookmarks, как BookmarkId, ToLookup могут быть полезны в этом случае:

var tagLookup = (from t in SessionFactory.GetCurrentSession().Query<Tag>() 
       // limit query appropriately for all the bookmarks you need 
       // this should be done once, in this optimization 
       select new {key=t.BookmarkId, value=t}) 
       .ToLookup(x=>x.key, x=>x.value); 

даст вам поиск (ILookup<int, Tag>), где вы можете сделать что-то вроде:

IGrouping<Tag> thisBookmarksTags = tagLookup[bookmarkId]; 

который предоставит вам подходящие метки для этого. Это отделяет его от другого запроса, тем самым избегая N + 1.

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

+0

Благодарим вас за ответ. Однако у меня есть много-много отношений между закладками и тегами. Это отображается следующим образом: HasManyToMany (x => x.Tags), поэтому у меня нет идентификатора закладки внутри тега. –

+0

Итак, у вас есть что-то вроде таблицы отношений BookmarkTags? Просто спросите, что тогда, построив поиск таким же образом. – dwerner

+0

Так что мне нужно сопоставить таблицу соединений? –

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