LinqToSolr — используем LINQ для получения данных из Solr

в 6:59, , рубрики: .net, C#, linq, solr

image
В силу того, что в нашей компании в качестве платформы полнотекстового поиска выбор пал на 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

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js