54

全文検索と Entity Framework に関連して多くの質問が寄せられていることは承知していますが、この質問が少し異なることを願っています。

Entity Framework、Code First を使用しており、全文検索を行う必要があります。全文検索を実行する必要がある場合、通常、最初の 500 行をスキップしたり、別の列でフィルター処理したりするなど、他の基準/制限も設定します。

これはテーブル値関数を使用して処理されていることがわかります - http://sqlblogcasts.com/blogs/simons/archive/2008/12/18/LINQ-to-SQL---Enabling-Fulltext-searching.aspxを参照してください。そして、これは正しい考えのようです。

残念ながら、テーブル値関数は Entity Framework 5.0 までサポートされていません (その場合でも、Code First ではサポートされていないと思います)。

私の本当の質問は、Entity Framework 4.3 と Entity Framework 5.0 の両方で、これを処理する最善の方法の提案は何かということです。しかし、具体的に言うと:

  1. 動的 SQL (System.Data.Entity.DbSet.SqlQueryたとえば、経由) 以外に、Entity Framework 4.3 で使用できるオプションはありますか?

  2. Entity Framework 5.0 にアップグレードする場合、最初にコードでテーブル値関数を使用する方法はありますか?

ありがとう、エリック

4

5 に答える 5

53

EF6 で導入されたインターセプターを使用すると、 http : //www.entityframework.info/Home/FullTextSearch で説明されているように、linq で全文検索をマークしてから dbcommand で置き換えることができます。

public class FtsInterceptor : IDbCommandInterceptor
{
    private const string FullTextPrefix = "-FTSPREFIX-";

    public static string Fts(string search)
    {
        return string.Format("({0}{1})", FullTextPrefix, search);
    }

    public void NonQueryExecuting(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    {
    }

    public void NonQueryExecuted(DbCommand command, DbCommandInterceptionContext<int> interceptionContext)
    {
    }

    public void ReaderExecuting(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
        RewriteFullTextQuery(command);
    }

    public void ReaderExecuted(DbCommand command, DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
    }

    public void ScalarExecuting(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    {
        RewriteFullTextQuery(command);
    }

    public void ScalarExecuted(DbCommand command, DbCommandInterceptionContext<object> interceptionContext)
    {
    }

    public static void RewriteFullTextQuery(DbCommand cmd)
    {
        string text = cmd.CommandText;
        for (int i = 0; i < cmd.Parameters.Count; i++)
        {
            DbParameter parameter = cmd.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;
                if (value.IndexOf(FullTextPrefix) >= 0)
                {
                    parameter.Size = 4096;
                    parameter.DbType = DbType.AnsiStringFixedLength;
                    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;
                    cmd.CommandText = Regex.Replace(text,
                        string.Format(
                            @"\[(\w*)\].\[(\w*)\]\s*LIKE\s*@{0}\s?(?:ESCAPE N?'~')",
                            parameter.ParameterName),
                        string.Format(@"contains([$1].[$2], @{0})",
                                    parameter.ParameterName));
                    if (text == cmd.CommandText)
                        throw new Exception("FTS was not replaced on: " + text);
                    text = cmd.CommandText;
                }
            }
        }
    }

}
static class LanguageExtensions
{
    public static bool In<T>(this T source, params T[] list)
    {
        return (list as IList<T>).Contains(source);
    }
}

たとえば、FTS インデックス フィールド NoteText を持つクラス Note がある場合:

public class Note
{
    public int NoteId { get; set; }
    public string NoteText { get; set; }
}

およびそのためのEFマップ

public class NoteMap : EntityTypeConfiguration<Note>
{
    public NoteMap()
    {
        // Primary Key
        HasKey(t => t.NoteId);
    }
}

およびそのコンテキスト:

public class MyContext : DbContext
{
    static MyContext()
    {
        DbInterception.Add(new FtsInterceptor());
    }

    public MyContext(string nameOrConnectionString) : base(nameOrConnectionString)
    {
    }

    public DbSet<Note> Notes { get; set; }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        modelBuilder.Configurations.Add(new NoteMap());
    }
}

FTS クエリの非常に単純な構文を使用できます。

class Program
{
    static void Main(string[] args)
    {
        var s = FtsInterceptor.Fts("john");

        using (var db = new MyContext("CONNSTRING"))
        {
            var q = db.Notes.Where(n => n.NoteText.Contains(s));
            var result = q.Take(10).ToList();
        }
    }
}

それは次のようなSQLを生成します

exec sp_executesql N'SELECT TOP (10) 
[Extent1].[NoteId] AS [NoteId], 
[Extent1].[NoteText] AS [NoteText]
FROM [NS].[NOTES] AS [Extent1]
WHERE contains([Extent1].[NoteText], @p__linq__0)',N'@p__linq__0 char(4096)',@p__linq__0='(john)   

ローカル変数を使用する必要があり、式内で FTS ラッパーを移動できないことに注意してください。

var q = db.Notes.Where(n => n.NoteText.Contains(FtsInterceptor.Fts("john")));
于 2013-10-28T20:54:14.123 に答える
19

これを実装する最も簡単な方法は、SQL Server で全文検索をセットアップして構成し、ストアド プロシージャを使用することです。引数を SQL に渡し、DB にジョブを実行させ、複雑なオブジェクトを返すか、結果をエンティティにマップします。必ずしも動的 SQL を使用する必要はありませんが、最適な場合があります。たとえば、ページングが必要な場合、動的 SQL を必要とせずに、要求ごとにPageNumberPageSizeを渡すことができます。ただし、クエリごとに引数の数が変動する場合は、最適なソリューションになります。

于 2012-08-14T17:24:47.463 に答える
5

他の人が言ったように、私は Lucene.NET の使用を開始すると言います

Lucene の習得にはかなりの時間がかかりますが、「 SimpleLucene 」と呼ばれるラッパーをCodePlexで見つけました。

ブログからいくつかのコードブロックを引用して、使い方がいかに簡単かをお見せしましょう。まだ使い始めたばかりですが、すぐに慣れました。

まず、リポジトリからいくつかのエンティティを取得するか、場合によっては Entity Framework を使用します

public class Repository
{
    public IList<Product> Products {
        get {
            return new List<Product> {
                new Product { Id = 1, Name = "Football" },
                new Product { Id = 2, Name = "Coffee Cup"},
                new Product { Id = 3, Name = "Nike Trainers"},
                new Product { Id = 4, Name = "Apple iPod Nano"},
                new Product { Id = 5, Name = "Asus eeePC"},
            };
        }
    }
}

次にやりたいことは、インデックス定義を作成することです

public class ProductIndexDefinition : IIndexDefinition<Product> {
    public Document Convert(Product p) {
        var document = new Document();
        document.Add(new Field("id", p.Id.ToString(), Field.Store.YES, Field.Index.NOT_ANALYZED));
        document.Add(new Field("name", p.Name, Field.Store.YES, Field.Index.ANALYZED));
        return document;
    }

    public Term GetIndex(Product p) {
        return new Term("id", p.Id.ToString());
    }
}

そのための検索インデックスを作成します。

var writer = new DirectoryIndexWriter(
    new DirectoryInfo(@"c:\index"), true);

var service = new IndexService();
service.IndexEntities(writer, Repository().Products, ProductIndexDefinition());

これで、検索可能なインデックスが作成されました。あとは探すだけ…!かなり驚くべきことを行うことができますが、次のように簡単にすることもできます: (より大きな例については、ブログまたは codeplex のドキュメントを参照してください)。

var searcher = new DirectoryIndexSearcher(
                new DirectoryInfo(@"c:\index"), true);

var query = new TermQuery(new Term("name", "Football"));

var searchService = new SearchService();

Func<Document, ProductSearchResult> converter = (doc) => {
    return new ProductSearchResult {
        Id = int.Parse(doc.GetValues("id")[0]),
        Name = doc.GetValues("name")[0]
    };
};

IList<Product> results = searchService.SearchIndex(searcher, query, converter);
于 2012-11-18T13:01:13.723 に答える
1

最近、同様の要件があり、Microsoft フルテキスト インデックス アクセス専用の IQueryable 拡張機能を作成することになりました

于 2013-06-22T11:09:19.193 に答える