В силу того, что в нашей компании в качестве платформы полнотекстового поиска выбор пал на Solr, возникло сильное желание упростить работу с запросами к Solr через использование LINQ выражений.
Перешерстив интернет на наличие альтернатив, я пришел к выводу, что на данный момент необходимой мне библиотеки в общем доступе нет. Максимум, что удалось найти, это очень частичную реализацию запросов в Solr.NET (и скептический комментарий самого автора).
Результатом стала маленькая библиотека LinqToSolr, которая содержит в себе реализацию интерфейса IQueriable<> с возможностью конвертации запросов в понятный Solr API и обратно.
Реализованные методы Enumerable
На данный момент доступны реализации следующих методов:
- Where
- First
- FirstOrDefault
- Select
- GroupBy
- GroupByFacets — дополнительный метод для работы с Facets
- Take
- Skip
- OrderBy
- ThenBy
- OrderByDescending
- ThenByDescending
Конфигурация
Итак, для начала необходимо сконфигурировать подключение и настроить соотношение нашей модели данных к индексам Solr.
Начнем с конфигурации, но сперва определим представление документа Solr в виде обычного класса:
public class MyProduct{
[JsonProperty("ProductId")]
public int Id{get;set;}
public string Name{get;set}
public string Group{get;set}
public double Price {get;set;}
public bool IsDeleted{get;set}
}
Выше представлен класс, описывающий индекс Solr, в котором есть типизированные поля Id, Name, Group, Price и IsDeleted. При сильном желании, можно воспользоваться свойством JsonProperty, точбы переопределить названия полей (на примере поля Id).
Созданим конфигурацию:
var config = new LinqToSolrRequestConfiguration("http://localhost:1433/")
.MapIndexFor<MyProduct>("MyProductIndex");
Что мы здесь видим? Во-первых, мы предоставляем адрес к Solr.
Далее, мы указываем проекцию наших классов к индексам Солар. Естественно предположить, что мы можем указывать сколько угодно классов к разным индексам. Единственное условие — один класс должен соотноситься с единственным индексом. Минимальная конфигурация готова.
Инициализируем сам сервис:
var service = new LinqToSolrService(config);
Все. Мы выполнили все условия, чтобы начать использовать Linq к Solr.
Примеры использования
Метод FirstOrDefault
service.AsQueriable<MyProduct>().FirstOrDefalult(x=> x.Id == 1);
Метод Where
Выбираем все документы по группе:
service.AsQueriable<MyProduct>().Where(x=>x.Group == "Group1").ToList();
Пример реализации использования функций внутри linq запросов:
service.AsQueriable<MyProduct>().Where(x=>x.Group.Contains("roup")).ToList();
service.AsQueriable<MyProduct>().Where(x=>x.Group.StartsWith("Gro")).ToList();
service.AsQueriable<MyProduct>().Where(x=>x.Group.EndsWith("up1")).ToList();
Пример поиска в массиве:
var groupsArr= new[] { "Group1", "Group2", "Group3" };
service.AsQueriable<MyProduct>().Where(x=> groupsArr.Contains(x.Group)).ToList();
Пример выборки с «больше-меньше»:
service.AsQueriable<MyProduct>().Where(x=> x.Price >= 500 && x.Price < 1000).ToList();
Используем несколько Where:
service.AsQueriable<MyProduct>()
.Where(x=> !x.IsDeleted)
.Where(x=>x.Name.Contains("somepartofthename"))
.ToList();
Сортируем документы
service.AsQueriable<MyProduct>()
.Where(x=> !x.IsDeleted)
.OrderByDescending(x=> x.Group) // DESC
.ThenBy(x=>x.Name) // ASC
.ToList();
Выбираем определенное кол-во
service.AsQueriable<MyProduct>().Where(x=> !x.IsDeleted).Take(100).Skip(400).ToList();
Метод Select
//Выбрать одно поле
service.AsQueriable<MyProduct>().Where(x=> !x.IsDeleted).Select(x=> x.Name).ToList();
//Выбрать несколько полей в dynamic-объект
service.AsQueriable<MyProduct>().Where(x=> !x.IsDeleted).Select(x=> new {x.Name, x.Group}).ToList();
Работа с Facets
service.AsQueriable<MyProduct>().Where(x=> !x.IsDeleted).GroupByFacets(x=>x.Name, x=>x.Group).ToList();
В примере выше мы запрашиваем Solr вернуть нам 2 группы — Name и Group.
Также можно использовать GroupBy метод. Он же в свою очередь вернет похожий вариант, но и добавит сгруппированные документы. Что лучше использовать — выбирать вам. Facets более быстрый, но нужно сделать 2 запрос к серверу, чтобы получить список документов. GroupBy — работает медленнее, так как возвращает, помимо групп, и сами документы уже отсортированные по группам.
Отладка и проверка
В любом случае взникает необходимость проверить запрос и ответ. Это можно сделать с помощью встроенного в сервис объекта LastResponse. Фактически, это представление ответа сервера Solr. Плюс, там же расположен Url запроса (LastRequestUrl), который можно использовать в брауезере, чтобы проверить, что на самом деле возвращает Solr.
Сервис
Естественно, использовать LinqToSolrService напрямую не очень удобно. Мы создаем наш собственный сервис, унаследованный от LinqToSolrService.
public class MySolrService : LinqToSolrService
{
public MySolrService(LinqToSolrRequestConfiguration config) : base (config)
{ }
public IQueryable<MyProduct> NotDeleted()
{
return AsQueryable<MyProduct>().Where(x=> !x.IsDeleted);
}
public ICollection<MyProduct> GetProducts(params int[] ids)
{
return NotDeleted().Where(x=> ids.Contains(x.Id)).OrderBy(x=>x.Name).ToList();
}
public MyProduct GetProduct(id)
{
return NotDeleted().FirstOrDefault(x=> x.Id == id);
}
public string[] GetGroups(id)
{
return NotDeleted().GroupBy(x=> x.Group).ToArray();
}
}
Итог
Надеюсь, кому-то, кто исползует Solr в .NET проектах, пригодятся и статься и библиотека.
На данный момент реализованы самые очевидные запросы.
В планах добавить поддержку Boost, Proximity и функции запросов.
Автор: Jholinar