技術日記
Entity SQLでtritonnの全文検索を行うEdit

Entity FrameworkでMySQL+tritonnを使って全文検索を使おうとして、無駄に長時間はまってしまったので、その件について。

Entity Frameworkでmatch against相当のSQL文を発行するには、Entity SQLを使ってネイティブSQL文を発行する必要がある。けど、なんかその辺のドキュメントがいまいちまとまっていない。

実際に使用するコマンドは、ExecuteStoreQuery(TElement) メソッド (String, Object[])で、シグニチャは、

public ObjectResult<TElement> ExecuteStoreQuery<TElement>(
    string commandText,
    params Object[] parameters
)

で、第2パラメータがObject[]になっていて、

parameters の値には、DbParameter オブジェクトの配列またはパラメーター値の配列を使用できます。 値だけが提供されている場合は、配列内の値の順序に基づいて DbParameter オブジェクトの配列が作成されます。
なんて書いてある。でも、DbParameterオブジェクトのコンストラクタは、

public abstract class DbParameter : MarshalByRefObject,
    IDbDataParameter, IDataParameter

と、抽象クラスになっているんで、直接生成できない。じゃあいったい何を使えばいいんだよ。

いろいろ探していたら、SqlParameterというのを見つけた。そこで、

var db = new dbContext(); 
string sql = "select * from table where match (field) against(@keyword)";
var parameters = new List<SqlParameter>();
parameters.Add(new SqlParameter("keyword", keyword));
var result = db.ExecuteStoreQuery<Table>(sql, parameters);

なんて感じで書いてみたが、「Parameter '@keyword' must be defined.」となってうまくいかない。 ちゃんとkeywordって名前のパラメータを追加しているのに何でだろう、ということでものすごく悩んだのだが、結局しょうもないミスだった。上の最後の行を、

var result = db.ExecuteStoreQuery<Table>(sql, parameters.ToArray());

とすれば動いた。そういやList<SqlParameter>で宣言していたんだから、Object[]に明示的に変換しなければならないのか。

ExecuteStoredQueryの第2引数が、paramsキーワードがついた可変長引数で宣言されているから、配列以外が渡されたときに自動的に可変長引数扱いになってしまったせいで、パラメータの型違いエラーもでず、単にkeywordが定義されていないとか言われてしまう。おかげで使っているオブジェクトの種類がだめなのかとか、長々悩んでしまった。実際に発行されるSQL文がどうなっているのかも簡単に知る方法が用意されていないから、どのレイヤーでのエラーなのかもわかりにくいし。

で、動かしてみたら今度は、MySqlParameterじゃなきゃだめだというエラーが出た。そうかSqlParameterってMS SQL Server向けのクラスなのか。MSプロダクトだとSQLほにゃららはMS SQLのことを指しているんだけど、字面的には汎用のRDB向けクラスのように見えるからわかりにくい。そこで、SqlParameterの部分をMySql.Data.MySqlClient.MySqlParameterに変える。最終的には、

var db = new dbContext(); 
string sql = "select * from table where match (field) against(@keyword)";
var parameters = new List&lt;MySqlParameter&gt;();
parameters.Add(new MySqlParameter("keyword", keyword));
var result = db.ExecuteStoreQuery<Table>(sql, parameters.ToArray());

とやることで動いた。なんかものすごく解決に時間がかかった。

ちなみに上記は@parameterName形式でSQL文中にパラメータを埋め込みたい場合の話で、{0}、{1}みたいにパラメータインデックス(パラメータ配列の序数)でパラメータを埋め込む場合は、

var db = new dbContext(); 
string sql = "select * from table where match (field) against({0})";
var parameters = new List&lt;object&gt;();
parameters.Add(keyword);
var result = db.ExecuteStoreQuery<Table>(sql, parameters.ToArray());

でいける。なんかもうたったこれだけのことでものすごくはまった。

ちなみにこうやってEntity SQLを使って検索した結果でも、ちゃんとEntity Objectに変換して扱えるんで、各行オブジェクトの機能に関してはADO.NET Entity Frameworkで自動生成されたモデルクラスを拡張する方法 « blog.ishinao.netで拡張したものをそのまま使える。

Published At2011-11-22 21:12Updated At2019-12-30 15:28