【问题标题】:Substring search in RavenDBRavenDB 中的子字符串搜索
【发布时间】:2012-04-01 04:48:36
【问题描述】:

我有一组 Idea 类型的对象

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

我想通过子字符串搜索这些对象。例如,当我有标题“idea”的对象时,我希望在输入“idea”的任何子字符串时找到它:i, id, ide , 想法, 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 识别这种方法?除此之外,我的方法是否适合这种搜索(按子字符串搜索)?

编辑

基本上,我想在这个搜索上构建自动完成功能,所以它需要很快。

顺便说一句:我正在使用 RavenDB - Build #960

【问题讨论】:

  • RavenDB 索引在服务器上运行,因此无法访问这样的自定义代码。您编写的索引变成了一个字符串,发送到服务器并在那里编译,StringExtension 代码不随之而来,因此出现错误。
  • 我知道这是服务器端的责任,但是有没有办法将我的自定义代码注入那里?也许使用反射?

标签: c# .net search substring ravendb


【解决方案1】:

我设法用以下代码在内存中做到了这一点:

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);
}

这让我免去了使用 Daniel Lang's post 描述的 Contains 方法搜索字符串的 RavenDB 方式的麻烦。

Contains的扩展方法是这样的:

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

【讨论】:

  • 问题在于您从 RavenDB 拉回所有客户文档,然后在内存中过滤它们(正如您所指出的)。这可能适用于一些文档,但是当您有 100 个甚至 1000 个文档时,您将不得不开始对它们进行分页,并且性能不会很好。
  • 因此,虽然 Daniel 帖子中概述的方法可能需要一些额外的工作,但性能更好,因为它在服务器上完成所有工作,然后只发回匹配的文档。
  • @MattWarren:毫无疑问,我的朋友。我在编写代码时考虑了这个含义,但我对当前的性能感到满意。也许我将来会改变这一点。我忘了提到我正在使用此代码来创建自动完成功能。顺便说一句,Daniel 的帖子并没有真正展示如何模仿 Contains 功能,因为它只使用 StartWith 和 EndWith。
  • 是的,请参阅最后一段中的更新,您只需使用前导通配符,例如 *atthe*。不过有更好的办法,看这个帖子groups.google.com/d/topic/ravendb/WHSLk5EQC_4/discussion
  • @MattWarren:只有一个字:太棒了!我很感激能成为 StackOverflow 的一员,美好的事情就这样发生了,并以我们的方式出现......
【解决方案2】:

您可以使用以下方法跨多个字段执行子字符串搜索:

(1)

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

您可以在this site 上查看:

"默认情况下,RavenDB 使用一个名为 用于所有内容的 LowerCaseKeywordAnalyzer。 (...) 的默认值 每个字段都是 Stores 中的 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* 相当昂贵,尤其是前导通配符。在这个post 你可以找到更多关于它的信息。据说,领先的通配符会强制 lucene 对索引进行全面扫描,因此会大大降低查询性能。 Lucene 在内部存储其索引(实际上是字符串字段的术语)按字母顺序排序并从左到右“读取”。这就是为什么搜索尾随通配符快而前导通配符慢的原因。

所以也可以使用x.Title.StartsWith("something"),但这显然不会搜索所有子字符串。如果您需要快速搜索,您可以更改要搜索的字段的索引选项以进行分析,但它不会搜索所有子字符串。

如果子字符串查询中有空格键,请查看此question 以获得可能的解决方案。 如需提出建议,请查看http://architects.dzone.com/articles/how-do-suggestions-ravendb

【讨论】:

    【解决方案3】:

    这似乎与 RavenDB fast substring search 重复

    这里没有提到的答案是使用名为 NGram 的自定义 Lucene 分析器

    【讨论】:

    • 嗨,很高兴了解 NGram,实际上这个问题是在另一个问题之前提出的,但仍然是同一个主题
    【解决方案4】:

    以防其他人遇到此问题。 Raven 3 有一个Search() 扩展方法,允许子字符串搜索。

    几个问题:

    • 特别注意底部的“查询转义”部分
    • 我没有在任何地方看到它,但它只有在 Search() 直接添加到 Query() 时才对我有用(即它们之间没有任何 Where()OrderBy() 等)

    希望这可以减轻一些人的挫败感。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-06-03
      • 1970-01-01
      • 1970-01-01
      • 2019-10-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多