Linq-to-EntitiesとRegexを友達にする方法

Entity Frameworkは、データベースを使用したシステムの開発を大幅に促進します。 このフレームワークの長所と短所については議論しませんが(もちろんその多くはありますが)、このようなシステムを開発する際に解決しなければならなかった実際的な問題の1つを検討します。



かなり多数のレコードを持つSQLiteデータベースがあり、このデータベースがSystem.Data.SQLiteおよびEntity Framework 6.0を介して.NETアプリケーションで使用されているとします 。 そして顧客は来て、標準の正規表現を使用して検索できるように、データベース内のレコードを検索するための新しい関数が必要だと言います。



この記事では、 Linq-リクエストで指定された正規表現の処理がサーバー側で行われることを実現した方法を説明します。これにより、処理が高速化され、すべてのデータの事前ダウンロードによるクライアントアプリケーションメモリの無意味な肥大化が防止されます。



実際、問題は何ですか?



しかし、一般に2つの問題があります。

まず、 Linq-to-Entitiesはデフォルトで標準呼び出しを変換しません。 SQLクエリのNET 正規表現メソッド( 正規表現および一致クラス)。 理解できる-すべてのDBMSが正規表現を計算できるわけではありません。 Linqプロバイダーを作成することもできますが、それでも少し手間をかけたいと思います(結局、 System.Data.SQLiteには既に準備が整ったプロバイダーがあります)。



第二に、クライアント側でそのような荒野をまったく調べず、ローカルで処理を実行することも可能です。 しかし、ソリューションが失敗することは明らかです。単純なフィルタリングを実行するには、最悪の場合、テーブルからすべてのデータをダウンロードしてから、すべてのレコードを破棄する必要があります。 そして、これはクライアント側のメモリ使用量とパフォーマンスの損失です。



幸いなことに、SQLiteが役立ちます。



SQLiteには特別なREGEXPステートメントがあります 。これは、ユーザー定義のregexp()関数の構文上のシュガーにすぎません。 デフォルトでは、この関数は実装されておらず、呼び出しによってエラーが発生するため、ユーザーはリクエストでこの関数を使用する前にこの関数を定義する必要があります



これに基づいて構築します。 タスクは次のとおりです。





それでは始めましょう。



System.Data.SQLiteには、ユーザー定義関数を登録する機能が既にあるため、これを使用します。

関数を実装するクラスを作成します。



[SQLiteFunction(Name = "REGEXP", Arguments = 2, FuncType = FunctionType.Scalar)] public class RegExSQLiteFunction : SQLiteFunction { public static SQLiteFunctionAttribute GetAttribute() { return (SQLiteFunctionAttribute)typeof(RegExSQLiteFunction).GetCustomAttributes(typeof(SQLiteFunctionAttribute), false).Single(); } public override object Invoke(object[] args) { try { //  ,       return Regex.IsMatch((string)args[1], (string)args[0]); } catch (Exception ex) { //   System.Data.SQLite,      return ex; } } }
      
      







この関数を登録するには、接続オブジェクトで対応するメソッドを呼び出します。

 var connection = new SQLiteConnection(connectionString); connection.Open(); connection.BindFunction(RegExSQLiteFunction.GetAttribute(), new RegExSQLiteFunction());
      
      







それだけです! これで、サーバーによって実行されるSQLクエリでREGEXP演算子を使用すると、実装が呼び出されます。 便利ですね。



Entity FrameworkのLinq経由でこれを呼び出す方法は?



これが「ブラックマジック」の始まりです。

Entity Frameworkモデルを作成するとき、新しい機能を説明する新しい契約を登録する必要があります。 これを行うには、たとえば、作成時にデータコンテキストクラスに次のメソッド呼び出しを追加できます。

 protected override void OnModelCreating(DbModelBuilder modelBuilder) { //       Code First //     modelBuilder.Conventions.Add(new RegexpFunctionConvention()); }
      
      







合意クラス自体が関数を記述し、 LinqアダプターSystem.Data.SQLiteに「理解可能」にします。

 public class RegexpFunctionConvention : IStoreModelConvention<EdmModel> { public void Apply(EdmModel item, DbModel model) { //     var patternParameter = FunctionParameter.Create("pattern", this.GetStorePrimitiveType(model, PrimitiveTypeKind.String), ParameterMode.In); var inputParameter = FunctionParameter.Create("input", this.GetStorePrimitiveType(model, PrimitiveTypeKind.String), ParameterMode.In); //   var returnValue = FunctionParameter.Create("result", this.GetStorePrimitiveType(model, PrimitiveTypeKind.Boolean), ParameterMode.ReturnValue); var function = this.CreateAndAddFunction(item, "regexp", new[] { patternParameter, inputParameter }, new[] { returnValue }); } private EdmFunction CreateAndAddFunction(EdmModel item, string name, IList<FunctionParameter> parameters, IList<FunctionParameter> returnValues) { EdmFunctionPayload payload = new EdmFunctionPayload { StoreFunctionName = name, Parameters = parameters, ReturnParameters = returnValues, Schema = this.GetDefaultSchema(item), IsBuiltIn = true // ,      }; EdmFunction function = EdmFunction.Create(name, this.GetDefaultNamespace(item), item.DataSpace, payload, null); item.AddItem(function); return function; } //    }
      
      







そして最後に行うことは、コンパイラの「スタブメソッド」を作成することです。そのため、 Linqプロバイダーは、その基本に基づいて、 SQLクエリで実際の関数を既に生成しています。



 internal static class R { [DbFunction("CodeFirstDatabaseSchema", "regexp")] public static bool Regexp(string pattern, string input) { //       throw new NotImplementedException(); } }
      
      







生きている例



準備作業をすべて完了したら、最終的に利点を享受し、サーバー側でも実行される簡潔なLinqクエリを作成できます(ただし、クライアント側でユーザー定義関数の実装を使用しますが、これはリクエストの処理と同じではありません) Linq-to-Objectsをローカルで使用)。



 //   entities  Item,     Name    pattern using (MyDataContext context = new MyDataContext(myConnection)) { var filteredItems = context.Items.Where(i => R.Regexp(pattern, i.Name)); }
      
      







したがって、サーバーでIQueryableを実行すると、その利点と利点がすべて得られます。

私の短い記事が同様の問題を解決するのに役立つなら、私はうれしいです。



All Articles