Entity Framework Core でフルテキストインデックスのテキスト検索で検索ランクで並び替える - C#

Entity Framework Core でフルテキストインデックスのテキスト検索で検索ランクで並び替えるコードを紹介します。

概要

こちらの記事でFREETEXTTABLE, CONTAINSTABLEを利用するとRANK列を参照して、一致度の大小を判定する方法を紹介しました。 この処理をEntity Framework Coreで実装したいです。
この記事では、Entity Framework CoreでFREETEXTTABLE, CONTAINSTABLEを利用するコードを紹介します。

方針

Entity Framework CoreにFREETEXTTABLEを呼び出す仕組みが現在は無いため、SQL文を実行する方針で実装します。

事前準備

テーブル

以下のデータのテーブルを準備します。
Working テーブル
idnamevaluecategorymemo
1Penguin300B南国にすむペンギンです
2Whale420M北極海のクジラです
3Moffu880NULLよくわからない生き物です
4Camel220M砂漠にすむラクダです
5Owl90B関東のフクロウです
6Duck120Bそこらへんのアヒルです
7Lizard60Rひっそりと生きるトカゲです
8Scorpion45C礫砂漠に生きるサソリです
9Bear180M北海道の森林に住むクマです
10WhiteBear260M北極圏で生きるシロクマです

DbContextの作成

Scaffold-DbContextコマンドを実行し、DbContextを作成します。 詳細な手順はこちらの記事を参照してください。

プログラム: RANK値を表示しなくてよい場合

UI

下図のフォームを作成します。今回のプログラムでは[button1] [button2]を利用します。

コード

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.EntityFrameworkCore;
using EntityFrameworkCoreFullTextIndex.models;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.Data.SqlClient;

namespace EntityFrameworkCoreFullTextIndex
{
  public partial class FormContainsTable : Form
  {
    public FormContainsTable()
    {
      InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
      SqlParameter my_query_param = new SqlParameter("@querytext", System.Data.SqlDbType.NChar);
      my_query_param.Value = textBox1.Text;


      IPentecSandBoxContext ctx = new IPentecSandBoxContext();

      string sqlstr = "select * from Working INNER JOIN CONTAINSTABLE(Working, (name, memo), @querytext) FT ON Working.Id = FT.[Key] ORDER BY FT.RANK DESC;";
      List<Working> result = ctx.Workings.FromSqlRaw(sqlstr, my_query_param).ToList();

      foreach (Working w in result) {
        textBox2.Text += string.Format("{0:d} : {1} - {2:g} - {3}\r\n", w.Id, w.Name.Trim(), w.Value, w.Memo.Trim());
      }

    }


    private void button2_Click(object sender, EventArgs e)
    {
      SqlParameter my_query_param = new SqlParameter("@querytext", System.Data.SqlDbType.NChar);
      my_query_param.Value = textBox1.Text;


      IPentecSandBoxContext ctx = new IPentecSandBoxContext();

      string sqlstr = "select * from Working INNER JOIN FREETEXTTABLE(Working, (name, memo), @querytext) FT ON Working.Id = FT.[Key] ORDER BY FT.RANK DESC;";
      List<Working> result = ctx.Workings.FromSqlRaw(sqlstr, my_query_param).ToList();


      foreach (Working w in result) {
        textBox2.Text += string.Format("{0:d} : {1} - {2:g} - {3}\r\n", w.Id, w.Name.Trim(), w.Value, w.Memo.Trim());
      }

    }

  }
}

解説

今回はSQL文を実行するため、検索ワードの文字列を SQLパラメーターで挿入しています。

SqlParameter オブジェクトを作成し、Valueプロパティにテキストボックスで入力された検索ワードの文字列を設定します。
      SqlParameter my_query_param = new SqlParameter("@querytext", System.Data.SqlDbType.NChar);
      my_query_param.Value = textBox1.Text;

DbContextオブジェクトを作成し、FromSqlRawメソッドを呼び出してSQL文を実行します。SQL文内にSQLパラメータの名称(今回の場合は@querytext)を記述します。 FromSqlRawメソッドの第一引数にSQL文の文字列、第二引数にSQLパラメーターオブジェクトを与えます。
結果は、Workingテーブルのレコード型である iQueryable<Working> で受け取れますが、ToList()メソッドを呼び出し、List<Working>オブジェクトで結果を取得します。
      IPentecSandBoxContext ctx = new IPentecSandBoxContext();

      string sqlstr = "select * from Working INNER JOIN CONTAINSTABLE(Working, (name, memo), @querytext) FT ON Working.Id = FT.[Key] ORDER BY FT.RANK DESC;";
      List<Working> result = ctx.Workings.FromSqlRaw(sqlstr, my_query_param).ToList();

結果レコードのList<Working>オブジェクトの要素をテキストボックスに表示します。
      foreach (Working w in result) {
        textBox2.Text += string.Format("{0:d} : {1} - {2:g} - {3}\r\n", w.Id, w.Name.Trim(), w.Value, w.Memo.Trim());
      }

button2 も同様のコードですが、実行するSQL文が FREETEXTTABLE となる違いがあります。

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。


検索ワードを入力し[button1]をクリックします。フルテキスト検索が実行され、該当するレコードが下部のテキストボックスに表示されます。


[button2]の場合も同様です。

プログラム: RANK値を表示する場合

先のプログラムでは、ランクの高い順に並べ替えてレコードの情報を画面に表示しました。通常の用途であれば、先のコードで問題ありませんが、 RANKの値もプログラムで取得したい場合は、コードを変更する必要があります。

UI

下図のフォームを作成します。今回のプログラムでは[button3] [button4]を利用します。

コード

はじめにRANKの値を格納するレコードのモデルクラスを作成します。名称は、WorkingWithRank とします。
WorkingWithRank.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EntityFrameworkCoreFullTextIndex.models;

  public partial class WorkingWithRank
  {
    public int Id { get; set; }

    public string? Name { get; set; }

    public decimal? Value { get; set; }

    public string? Category { get; set; }

    public string? Memo { get; set; }

    public int? RANK { get; set; }
  }

Scaffold-DbContextコマンドで作成した、DbContextのクラスのコードを修正します。 WorkingWithRankクラスのDbSetプロパティを追加します。

変更前:(データベース名)Context.cs - この例の場合は IPentecSandBoxContext.cs
    public virtual DbSet<Working> Workings { get; set; }
変更後:(データベース名)Context.cs - この例の場合は IPentecSandBoxContext.cs
WorkingWithRank.cs
    public virtual DbSet<Working> Workings { get; set; }

    public virtual DbSet<WorkingWithRank> WorkingsWithRank { get; set; }

フォームに以下のコードを記述します。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using Microsoft.EntityFrameworkCore;
using EntityFrameworkCoreFullTextIndex.models;
using Microsoft.EntityFrameworkCore.Metadata.Internal;
using Microsoft.Data.SqlClient;

namespace EntityFrameworkCoreFullTextIndex
{
  public partial class FormContainsTable : Form
  {
    public FormContainsTable()
    {
      InitializeComponent();
    }

    private void button3_Click(object sender, EventArgs e)
    {
      SqlParameter my_query_param = new SqlParameter("@querytext", System.Data.SqlDbType.NChar);
      my_query_param.Value = textBox1.Text;

      IPentecSandBoxContext ctx = new IPentecSandBoxContext();

      string sqlstr = "select * from Working INNER JOIN CONTAINSTABLE(Working, (name, memo), @querytext) FT ON Working.Id = FT.[Key] ORDER BY FT.RANK DESC;";
      List<WorkingWithRank> result = ctx.WorkingsWithRank.FromSqlRaw(sqlstr, my_query_param).ToList();

      foreach (WorkingWithRank w in result) {
        textBox2.Text += string.Format("{0:d} : {1} - {2:g} - {3} : {4:d}\r\n", w.Id, w.Name.Trim(), w.Value, w.Memo.Trim(), w.RANK);
      }
    }

    private void button4_Click(object sender, EventArgs e)
    {
      SqlParameter my_query_param = new SqlParameter("@querytext", System.Data.SqlDbType.NChar);
      my_query_param.Value = textBox1.Text;

      IPentecSandBoxContext ctx = new IPentecSandBoxContext();

      string sqlstr = "select * from Working INNER JOIN FREETEXTTABLE(Working, (name, memo), @querytext) FT ON Working.Id = FT.[Key] ORDER BY FT.RANK DESC;";
      List<WorkingWithRank> result = ctx.WorkingsWithRank.FromSqlRaw(sqlstr, my_query_param).ToList();

      foreach (WorkingWithRank w in result) {
        textBox2.Text += string.Format("{0:d} : {1} - {2:g} - {3} : {4:d}\r\n", w.Id, w.Name.Trim(), w.Value, w.Memo.Trim(), w.RANK);
      }
    }
  }
}

解説

SQL文を実行するため、検索ワードの文字列を SQLパラメーターで挿入しています。

SqlParameter オブジェクトを作成し、Valueプロパティにテキストボックスで入力された検索ワードの文字列を設定します。
      SqlParameter my_query_param = new SqlParameter("@querytext", System.Data.SqlDbType.NChar);
      my_query_param.Value = textBox1.Text;

DbContextオブジェクトを作成し、FromSqlRawメソッドを呼び出してSQL文を実行します。SQL文内にSQLパラメータの名称(今回の場合は@querytext)を記述します。 FromSqlRawメソッドの第一引数にSQL文の文字列、第二引数にSQLパラメーターオブジェクトを与えます。
結果は、Workingテーブルのレコード型である iQueryable<WorkingWithRank> で受け取れますが、ToList()メソッドを呼び出し、List<WorkingWithRank>オブジェクトで結果を取得します。
      IPentecSandBoxContext ctx = new IPentecSandBoxContext();

      string sqlstr = "select * from Working INNER JOIN CONTAINSTABLE(Working, (name, memo), @querytext) FT ON Working.Id = FT.[Key] ORDER BY FT.RANK DESC;";
      List<WorkingWithRank> result = ctx.WorkingsWithRank.FromSqlRaw(sqlstr, my_query_param).ToList();

結果レコードのList<WorkingWithRank>オブジェクトの要素をテキストボックスに表示します。
      foreach (WorkingWithRank w in result) {
        textBox2.Text += string.Format("{0:d} : {1} - {2:g} - {3} : {4:d}\r\n", w.Id, w.Name.Trim(), w.Value, w.Memo.Trim(), w.RANK);
      }

button4 も同様のコードですが、実行するSQL文が FREETEXTTABLE となる違いがあります。

実行結果

プロジェクトを実行します。下図のウィンドウが表示されます。


検索ワードを入力し[button3]をクリックします。フルテキスト検索が実行され、該当するレコードが下部のテキストボックスに表示されます。 先の例と異なり、RANKの値もテキストボックスに表示されます。


[button4]の場合も同様です。RANKの値が結果と合わせてテキストボックスに表示されます。


著者
iPentecのメインプログラマー
C#, ASP.NET の開発がメイン、少し前まではDelphiを愛用
最終更新日: 2023-07-01
作成日: 2023-06-28
iPentec all rights reserverd.