Поиск подстроки в RavenDB - PullRequest
10 голосов
/ 15 марта 2012

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

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

Я хочу искать эти объекты по подстроке.Например, когда у меня есть объект заголовка « idea », я хочу, чтобы он был найден при вводе любой подстроки в « idea »: 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 распознавать этот метод?Кроме того, подходит ли мой подход для этого вида поиска (поиск по подстроке)?

EDIT

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

enter image description here

Кстати: я использую RavenDB - сборка # 960

Ответы [ 4 ]

9 голосов
/ 20 сентября 2012

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

(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: EscapeQueryOptions.AllowAllWildcards,
                            options: SearchOptions.And)
                    .As<Idea>();

Резюме:

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

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

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

2 голосов
/ 10 октября 2012

Похоже, что это дубликат RavenDB быстрый поиск подстроки

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

1 голос
/ 28 апреля 2016

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

Несколько уловок:

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

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

0 голосов
/ 16 марта 2012

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

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 для поиска строк с помощью метода Contains, как описано в Пост Даниэля Ланга .

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

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