Поиск подстроки в RavenDB

У меня есть набор объектов типа Idea

public class Idea
{
    public string Title { get; set; }
    public string Body { get; set; }
}

Я хочу искать объекты по подстроке. Например, когда у меня есть объект "идея", я хочу, чтобы он был найден, когда я ввожу какую-либо подстроку "идеи": i, id, ide, idea, d, de, dea, e, ea, a.

Я использую RavenDB для хранения данных. Поисковый запрос выглядит так:

var ideas = session
              .Query<IdeaByBodyOrTitle.IdeaSearchResult, IdeaByBodyOrTitle>()
              .Where(x => x.Query.Contains(query))
              .As<Idea>()
              .ToList();

а индекс следующий:

public class IdeaByBodyOrTitle : AbstractIndexCreationTask<Idea, IdeaByBodyOrTitle.IdeaSearchResult>
{
    public class IdeaSearchResult
    {
        public string Query;
        public Idea Idea;
    }

    public IdeaByBodyOrTitle()
    {
        Map = ideas => from idea in ideas
                       select new
                           {
                               Query = new object[] { idea.Title.SplitSubstrings().Concat(idea.Body.SplitSubstrings()).Distinct().ToArray() },
                               idea
                           };
        Indexes.Add(x => x.Query, FieldIndexing.Analyzed);
    }
}

SplitSubstrings() - это метод расширения, который возвращает все различные подстроки заданной строки:

static class StringExtensions
{
    public static string[] SplitSubstrings(this string s)
    {
        s = s ?? string.Empty;
        List<string> substrings = new List<string>();
        for (int i = 0; i < s.Length; i++)
        {                
            for (int j = 1; j <= s.Length - i; j++)
            {
                substrings.Add(s.Substring(i, j));
            }
        }            
        return substrings.Select(x => x.Trim()).Where(x => !string.IsNullOrEmpty(x)).Distinct().ToArray();
    }
}

Это не работает. В частности, поскольку RavenDB не распознает метод SplitSubstrings(), потому что он находится в моей пользовательской сборке. Как сделать эту работу, в основном, как заставить RavenDB распознать этот метод? Кроме того, подходит ли мой подход для такого поиска (поиск подстрокой)?

ИЗМЕНИТЬ

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

enter image description here

Btw: Я использую RavenDB - Build # 960

Ответ 1

Вы можете выполнять поиск подстроки по нескольким полям, используя следующий подход:

(1)

public class IdeaByBodyOrTitle : AbstractIndexCreationTask<Idea>
{
    public IdeaByBodyOrTitle()
    {
        Map = ideas => from idea in ideas
                       select new
                           {
                               idea.Title,
                               idea.Body
                           };
    }
}

на этот сайт вы можете проверить, что:

"По умолчанию RavenDB использует собственный анализатор, называемый LowerCaseKeywordAnalyzer для всего содержимого. (...) Значения по умолчанию для каждое поле - FieldStorage.No в магазинах и FieldIndexing.Default в Индексы".

Итак, по умолчанию, если вы проверяете термины индекса внутри клиента raven, он выглядит следующим образом:

Title                    Body
------------------       -----------------
"the idea title 1"       "the idea body 1"
"the idea title 2"       "the idea body 2" 

Исходя из этого, можно создать шаблонный запрос:

var wildquery = string.Format("*{0}*", QueryParser.Escape(query));

который затем используется с конструкциями .In и .Where (с использованием оператора OR внутри):

var ideas = session.Query<User, UsersByDistinctiveMarks>()
                   .Where(x => x.Title.In(wildquery) || x.Body.In(wildquery));

(2)

В качестве альтернативы вы можете использовать чистый запрос lucene:

var ideas = session.Advanced.LuceneQuery<Idea, IdeaByBodyOrTitle>()
                   .Where("(Title:" + wildquery + " OR Body:" + wildquery + ")");

(3)

Вы также можете использовать выражение .Search, но вам нужно построить свой индекс по-разному, если вы хотите искать по нескольким полям:

public class IdeaByBodyOrTitle : AbstractIndexCreationTask<Idea, IdeaByBodyOrTitle.IdeaSearchResult>
{
    public class IdeaSearchResult
    {
        public string Query;
        public Idea Idea;
    }

    public IdeaByBodyOrTitle()
    {
        Map = ideas => from idea in ideas
                       select new
                           {
                               Query = new object[] { idea.Title, idea.Body },
                               idea
                           };
    }
}

var result = session.Query<IdeaByBodyOrTitle.IdeaSearchResult, IdeaByBodyOrTitle>()
                    .Search(x => x.Query, wildquery, 
                            escapeQueryOptions: scapeQueryOptions.AllowAllWildcards,
                            options: SearchOptions.And)
                    .As<Idea>();

Резюме:

Также имейте в виду, что *term* довольно дорогой, особенно главный шаблон. В этом post вы можете найти дополнительную информацию об этом. Говорят, что ведущая подстановка заставит lucene выполнить полное сканирование индекса и, таким образом, может резко замедлить выполнение запросов. Lucene внутренне сохраняет свои индексы (на самом деле термины строковых полей), отсортированные по алфавиту и "читаемые" слева направо. Именно по этой причине быстро выполняется поиск конечного шаблона и медленный для ведущего.

Таким образом, можно использовать x.Title.StartsWith("something"), но это, очевидно, не поиск по всем подстрокам. Если вам нужен быстрый поиск, вы можете изменить параметр "Индекс" для полей, которые вы хотите искать, для анализа, но он снова не будет искать по всем подстрокам.

Если в запросе подстроки есть пробел, проверьте это question для возможного решения. Для внесения предложений проверьте http://architects.dzone.com/articles/how-do-suggestions-ravendb.

Ответ 2

Это, по-видимому, дубликат быстрого поиска подстроки RavenDB

Ответ там, который здесь не упоминался, заключается в использовании пользовательского анализатора Lucene под названием NGram

Ответ 3

Если кто-нибудь еще столкнется с этим. У Raven 3 есть Search() метод расширения, который позволяет искать подстроку.

Несколько ошибок:

  • Обратите особое внимание на раздел "Уклонение от запроса" внизу
  • Я не видел, чтобы это упоминалось где-нибудь, но это работало только для меня, если Search() был добавлен непосредственно к Query() (т.е. без каких-либо Where(), OrderBy() и т.д. между ними)

Надеюсь, это избавит кого-то от разочарования.

Ответ 4

Мне удалось сделать это в памяти со следующим кодом:

public virtual ActionResult Search(string term)
{
    var clientNames = from customer in DocumentSession.Query<Customer>()
                        select new { label = customer.FullName };

    var results = from name in clientNames.ToArray()
                    where name.label.Contains(term,
                                             StringComparison.CurrentCultureIgnoreCase)
                    select name;

    return Json(results.ToArray(), JsonRequestBehavior.AllowGet);
}

Это избавило меня от необходимости искать способ RavenDB для поиска строк с помощью метода Содержит, описанный Daniel Lang post.

Метод расширения Contains таков:

public static bool Contains(this string source, string toCheck, StringComparison comp)
{
     return source.IndexOf(toCheck, comp) >= 0;
}