Функциональность поиска с использованием шаблона GenericRepository и SearchService

#asp.net-mvc #entity-framework #search #repository-pattern #entity-framework-4.1

#asp.net-mvc #entity-framework #Поиск #репозиторий-шаблон #entity-framework-4.1

Вопрос:

Я использую шаблон Generic Repository для абстрагирования моего EF 4.1 DbContext. Я также использую уровень сервиса для запроса и фиксации изменений в репозитории (ах). В любом случае, чего я пытаюсь добиться здесь, это разрешить пользователю вводить поисковую фразу в строку поиска. Затем SearchService должен запросить базу данных, чтобы найти любые категории верхнего уровня, подкатегории или элементы, которые содержат поисковую фразу в своих названиях. На первый взгляд, я думал, что это будет просто сделать, но, по-видимому, все немного сложнее. Вот что я пытался сделать:

     public IList<Item> Search(string searchPhrase)
    {
        var result = new List<Item>();

        var tmp = from c in _repository.GetQuery<TopLevelCategory>(c=>c.Children)
                  where c.Name.Contains(searchPhrase)
                  select c;
        if(tmp.Count() > 0)
        {
            foreach (var c in tmp)
            {
                var children = c.Children;
                foreach (var childCategory in children)
                {
                    result.Concat(childCategory.Items);
                }
            }
        }

        var tmp2 = from c in _repository.GetQuery<ChildCategory>(c=>c.Items)
              where c.Name.Contains(searchPhrase)
              select c;
        if(tmp.Count() > 0)
        {
            foreach (var childCategory in tmp2)
            {
                result.Concat(childCategory.Items);
            }
        }

        var tmp3 = from c in _repository.GetQuery<Item>()
                   where c.Title.Contains(searchPhrase)
                   select c;
        if(tmp3.Count() > 0)
        {
            result.Concat(tmp3);
        }
        return resu<
    }
}
  

Я знаю, что это выглядит некрасиво и запутанно, но я просто попробовал посмотреть, возвращает ли он правильные результаты. Ну, это не так, это вызвало следующее исключение:

Сведения об исключении: Система.Исключение InvalidOperationException: С этой командой уже связан открытый DataReader, который должен быть закрыт первым.

 Source:

Line 31:          foreach (var childCategory in children)
Line 32:          {
Line 33:                 result.Concat(childCategory.Items);
Line 34:          }
  

В любом случае, должен быть более разумный способ сделать это… Есть предложения?

Ответ №1:

В исключении говорится, что вы открыли DataReader для загрузки категорий верхнего уровня с их дочерними элементами, но вы запускаете отложенную загрузку во внутреннем цикле foreach. Для этого требуется другой DataReader для открытия и чтения отложенных загруженных элементов:

 // Iterate top level categories => fist openned DataReader
foreach (var c in tmp)
{
    // Child category is eager loaded
    var children = c.Children;
    foreach (var childCategory in children)
    {
        // Items are not eager loaded => trigger lazy loading and open new DataReader
        result.Concat(childCategory.Items);
    }
}
  

Чтобы решить эту проблему, вы должны изменить строку подключения и добавить поддержку MARS MultipleActiveResultSets=true . MARS поддерживается по крайней мере SQL Server 2005 и 2008. Другой способ избежать этого — также загружать элементы с нетерпением.

Это больше похоже на задачу для полнотекстового поиска на уровне базы данных.

Комментарии:

1. @Ladislav Mrnka: Спасибо за ответ. Имеет полный смысл. Да, вы правы, я включаю категории подуровня в категории верхнего уровня, но я не знаю, могу ли я также включить элементы. Это потому, что для элементов в классе категории верхнего уровня нет свойства навигации. Есть ли способ обойти это? А как насчет MARS, влияет ли это как-нибудь на производительность?

2. MARS — это режим по умолчанию, если вы разрешаете EF создавать строку подключения (например, при использовании EDMX). В вашем сценарии нет способа избежать MARS. В этом случае не должно быть никаких проблем с производительностью. Для описания производительности, связанной с MARS, смотрите: msdn.microsoft.com/en-us/library /…

3. @Ladislav Mrnka: Я добавил MARS в строку подключения и отладил ее. По-видимому, никакие элементы не извлекаются с помощью запроса категорий верхнего уровня, потому что я не включаю объект Item, поэтому мне нужно обойти это… И, кстати, по какой-то причине запрос выполняется внутри бесконечного цикла, вызывающего исключение StackOverflowException. Есть мысли?

4. Какая часть получает бесконечный цикл?

5. @Ladislav Mrnka: Я не совсем уверен, это показывает мне, что ошибка возникает из @Html.Action("Search", "Item") вызова внутри моего представления. Но я думаю, что это на уровне категорий верхнего уровня…

Ответ №2:

Ошибка, которую вы получаете, может быть связана с MARS. Вам просто нужно включить несколько активных наборов результатов (MARS), просто добавьте ‘MultipleActiveResultSets= True’ в строку подключения. Проверьте здесь

  1. Вы можете создать представление, которое объединяет имена на верхнем уровне, дочернем уровне и элементах. Затем предоставьте это представление как объект. Помните, что у EF будут проблемы с представлениями, поскольку его разработчик пытался определить поле ID.
  2. Вы можете посмотреть на lucene на случай, если считаете, что вам требуются более надежные возможности запросов.