【问题标题】:Full Text Search in LinqLinq 中的全文搜索
【发布时间】:2010-10-08 14:44:15
【问题描述】:

Linq 中没有内置全文搜索,而且似乎没有很多关于该主题的帖子,所以我尝试了一下,并为我的实用程序类想出了这个方法:

public static IEnumerable<TSource> GenericFullTextSearch<TSource>(string text, MyDataContext context)
{
    //Find LINQ Table attribute
    object[] info = typeof(TSource).GetCustomAttributes(typeof(System.Data.Linq.Mapping.TableAttribute), true);
    //Get table name
    String table = (info[0] as System.Data.Linq.Mapping.TableAttribute).Name;
    //Full text search on that table
    return context.ExecuteQuery<TSource>(String.Concat("SELECT * FROM ", table, " WHERE CONTAINS(*, {0})"), text);
}

并将这个包装器添加到每个有全文索引的部分 Linq 类中

public static IEnumerable<Pet> FullTextSearch(string text, MyDataContext context)
{
    return (LinqUtilities.GenericFullTextSearch<Pet>(text, context) as IEnumerable<Pet>);
}

所以现在我可以使用诸如

之类的简洁内容进行全文搜索
var Pets = Pet.FullTextSearch(helloimatextbox.Text, MyDataContext).Skip(10).Take(10);

我假设目前只需要非常基本的搜索。有人可以改进吗?是否可以作为扩展方法实现并避免包装器?

【问题讨论】:

  • 关于您的查询的一个危险/非最佳问题是 .Skip().Take() 将在客户端而不是服务器端执行。因此,如果您执行一个返回 10^6 个结果的 FTS,并且您只想拥有前 10 个结果,那么所有 10^6 个结果都将从数据库中返回,然后您才会执行过滤。
  • 是的,在这么大的数据集上,我会考虑另一种技术;)

标签: linq linq-to-sql search full-text-search


【解决方案1】:

最简洁的解决方案是在 sql 中使用内联表值函数并将其添加到您的模型中

http://sqlblogcasts.com/blogs/simons/archive/2008/12/18/LINQ-to-SQL---Enabling-Fulltext-searching.aspx

要使其正常工作,您需要创建一个表值函数 只不过是基于您的关键字的CONTAINSTABLE 查询 进来,

create function udf_sessionSearch
      (@keywords nvarchar(4000)) returns table as   return (select [SessionId],[rank]
            from containstable(Session,(description,title),@keywords))

然后,您将此函数添加到您的 LINQ 2 SQL 模型中,然后他会预先通知您 现在可以写查询了。

var sessList = from s   in DB.Sessions
               join fts in DB.udf_sessionSearch(SearchText) on s.sessionId equals fts.SessionId
               select s;

【讨论】:

  • 据我所知,这也需要每个表的 udf,OPs 解决方案应该适用于所有表。谢谢你的帖子
【解决方案2】:

我对缺乏明确的示例感到非常沮丧......尤其是当存在潜在的大型数据集并且需要分页时。因此,这是一个示例,希望包含您可能需要的所有内容:-)

create function TS_projectResourceSearch
    (   @KeyStr nvarchar(4000), 
        @OwnId int,
        @SkipN int,
        @TakeN int )
    returns @srch_rslt table (ProjectResourceId bigint not null, Ranking int not null )
    as 
    begin

        declare @TakeLast int
        set @TakeLast = @SkipN + @TakeN
        set @SkipN = @SkipN + 1

        insert into @srch_rslt  
        select pr.ProjectResourceId, Ranking
        from 
        (
            select t.[KEY] as ProjectResourceId, t.[RANK] as Ranking, ROW_NUMBER() over (order by t.[Rank] desc) row_num
            from containstable( ProjectResource,(ResourceInfo, ResourceName), @KeyStr )
            as t        
        ) as r
        join ProjectResource pr on r.ProjectResourceId = pr.ProjectResourceId
        where (pr.CreatorPersonId = @OwnId
            or pr.ResourceAvailType < 40)
            and r.row_num between @SkipN and @TakeLast
        order by r.Ranking desc 

        return
    end
    go


    select * from ts_projectResourceSearch(' "test*" ',1002, 0,1)

享受, 帕特里克

【讨论】:

    【解决方案3】:

    我使用Provider Wrapper 技术使用了一个小技巧。我有一个 c# 代码,用 FTS 搜索 MS SQL 重写 SQL 中的魔法词(你可以调整你喜欢的任何服务器)。

    如果你有上下文类 MyEntities,创建类似的子类

    public class MyEntitiesWithWrappers : MyEntities
    {
        private IEFTraceListener listener;
        public string FullTextPrefix = "-FTSPREFIX-";
    
        public MyEntitiesWithWrappers(): this("name=MyEntities")
        {
        }
    
        public MyEntitiesWithWrappers(string connectionString)
            : base(EntityConnectionWrapperUtils.CreateEntityConnectionWithWrappers(connectionString,"EFTracingProvider"))
        {
            TracingConnection.CommandExecuting += RewriteFullTextQuery;
        }
    
        /// <summary>
        /// Rewrites query that contains predefined prefix like: where n.NOTETEXT.Contains(Db.FullTextPrefix  + text) with SQL server FTS 
        /// To be removed when EF will support FTS
        /// </summary>
        /// <param name="o"></param>
        /// <param name="args"></param>
        public void RewriteFullTextQuery(object o, CommandExecutionEventArgs args)
        {
            var text = args.Command.CommandText;
            for (int i = 0; i < args.Command.Parameters.Count; i++)
            {
                DbParameter parameter = args.Command.Parameters[i];
                if (parameter.DbType.In(DbType.String, DbType.AnsiString, DbType.StringFixedLength, DbType.AnsiStringFixedLength))
                {
                    if (parameter.Value == DBNull.Value)
                        continue;
                    var value = (string) parameter.Value;
                    parameter.Size = 4096;
                    if (value.IndexOf(FullTextPrefix) >= 0)
                    {
                        value = value.Replace(FullTextPrefix, ""); // remove prefix we added n linq query
                        value = value.Substring(1, value.Length-2); // remove %% escaping by linq translator from string.Contains to sql LIKE
                        parameter.Value = value;
                        args.Command.CommandText = Regex.Replace(text,
                            string.Format(@"\(\[(\w*)\].\[(\w*)\]\s*LIKE\s*@{0}\s?(?:ESCAPE '~')\)", parameter.ParameterName), 
                            string.Format(@"contains([$1].[$2], @{0})", parameter.ParameterName));
                    }
                }
            }
        }
    }
    

    然后像这样使用它:

    var fullTextSearch = Db.FullTextPrefix + textToSearch;
    var q = Db.Notes.Where(n => !n.Private && n.NoteText.Contains(fullTextSearch));
    

    【讨论】:

      【解决方案4】:

      使用 CONTAINSTABLE 的更好的方法(使排名生效)

      String pkey = context.Mapping.GetTable(typeof(TSource)).RowType.DataMembers.SingleOrDefault(x => x.IsPrimaryKey).Name;
      string query = String.Concat(@"SELECT *
          FROM ", table, @" AS FT_TBL INNER JOIN
          CONTAINSTABLE(", table, @", *, {0}) AS KEY_TBL
          ON FT_TBL.", pkey, @" = KEY_TBL.[KEY]
          ORDER BY KEY_TBL.[RANK] DESC");
      return context.ExecuteQuery<TSource>(query, text);
      

      【讨论】:

        【解决方案5】:

        .NET Core 2.1 及更高版本支持允许使用FREETEXTFREETEXTTABLE 搜索的扩展方法

        using Microsoft.EntityFrameworkCore;
        
        var results = dbContext.MyTable
                .Where(e => EF.Functions.FreeText("*", "search criteria"));
        

        FreeText 函数在Microsoft.EntityFrameworkCore.SqlServer 中定义,因此您的项目必须引用该包。

        Documentation

        【讨论】:

          【解决方案6】:

          我一直在尝试解决确切的问题。我喜欢在我的 LINQtoSQL 中编写我的 SQL 逻辑,但我需要一种方法来进行全文搜索。现在我只是使用 SQL 函数,然后在 linq 查询中调用用户定义的函数。不确定这是否是最有效的方法。大家觉得呢?

          【讨论】:

            【解决方案7】:

            你可以这样做

                var results = (from tags in _dataContext.View_GetDeterminationTags
                               where tags.TagName.Contains(TagName) ||
                               SqlMethods.Like(tags.TagName,TagName)
                               select new DeterminationTags
                               {
                                   Row = tags.Row,
                                   Record = tags.Record,
                                   TagID = tags.TagID,
                                   TagName = tags.TagName,
                                   DateTagged = tags.DateTagged,
                                   DeterminationID = tags.DeterminationID,
                                   DeterminationMemberID = tags.DeterminationMemberID,
                                   MemberID = tags.MemberID,
                                   TotalTagged = tags.TotalTagged.Value
                               }).ToList();
            

            注意 TagName.Contains 也包含 SQLMethods.Like 只是做一个使用

            using System.Data.Linq.SqlClient;
            

            获取对该 SQLMethods 的访问权限。

            【讨论】:

            • .contains 转换为 LIKE '%TAGNAME%',这是次优的。
            • LIKE 不是 FULLTEXT 搜索。
            【解决方案8】:

            dswatik - 想要全文搜索的原因是 .contains 转换为

            SELECT * FROM MYTABLE WHERE COLUMNNAME LIKE '%TEXT%'
            

            忽略任何索引并且在大表上很糟糕。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2021-10-24
              • 2013-03-08
              • 1970-01-01
              相关资源
              最近更新 更多