Entity Framework Coreにオペレーターを実装します

かつて、3月の土曜日の曇りの朝に、Entity FrameworkのマスタードをEntity Framework Coreに変換する上でMicrosoftがどのように取り組んでいるかを確認することにしました。 ちょうど1年前、私たちのチームが新しいプロジェクトを開始し、ORMを選択したとき、私たちの手は、可能な限りスタイリッシュで若々しいものを使用するためにかゆみを覚えました。 しかし、EFCを見ると、まだ非常に遠く離れた生産であることがわかりました。 N + 1クエリ(第2バージョンで大幅に改善)、曲がったネストされた選択( 2.1.0-preview1で修正 )、多対多のサポートなし(まだではない)、ケーキのチェリー-DbGeometryサポートの欠如、に多くの問題があります。これは私たちのプロジェクトで非常に重要でした。 最後の機能は、2015年以降プロジェクトのロードマップに優先度の高いリストとして含まれていることは注目に値します。 私たちのチームは、「このタスクを優先度の高いリストに追加します」というトピックについて冗談を言っています。 そして、EFCの最後の改訂から1年が経過し、この製品の2番目のバージョンがすでにリリースされていたので、状況を確認することにしました。


私の意見では、製品をテストする最良の方法の1つは、カスタム機能を使用して製品を拡張することです。 これにより、次のことがすぐにわかります。a)アーキテクチャの品質。 b)文書の品質。 c)コミュニティサポート。


Googleの結果の最初のページをざっと見てみると、EFCでの全文検索はまだサポートされていませんが、計画があります。 それが必要です。T-SQLからCONTAINS述語を自分で実装してみることができます。


APIを作成します


私は複雑なメソッドを気にせず、単に文字列の拡張メソッドを宣言しました:


 public static class StringExt { public static bool ContainsText(this string text, string sub) { throw new NotImplementedException("This method is not supposed to run on client"); } } 

メソッドの本体では、単にマーカーであり、クライアントで起動することを意図していないため、単に例外をスローします。 カスタムコードでは、次のようになります。


 dbContext.Posts.Where(x => x.Content.ContainsText("egg")); 

これを実装する方法を理解することは残っています。


拡張ポイントを検索する


これにより事態はより複雑になります。 Googleは、「ef core create custom operator」のリクエストに応じて、プロジェクトのgithubからのトピックへのリンクのみを提供し、「hey、any update on?」などのメッセージで終わります。 また、SQLクエリを手動で実行することをお勧めします。これは確かに機能しますが、これはオプションではありません。


新しいことをする最良の方法は、類推によってそれをすることです。 実装する最も近い演算子は何ですか? そう、 LIKELIKEステートメントは、 String.Containsメソッドから変換されます。 必要なのは、EFC開発者がこれをどのように行っているかを覗くだけです。


リポジトリをダウンロードして、Visual Studio 2017で開き、... Visual Studioが完全に停止します。 まあ、わかりました、アマチュア向けの太ったIDEです。VisualStudio Codeを使用します。 さらに、Code Lensは箱から出してすぐに使用できます。


候補に含まれるSqlServerContainsOptimizedTranslator.csという名前のファイルを見つけます。 何がそんなに最適化されているのでしょうか? EFCは、EFとは異なり、 LIKE '%pattern%'代わりにCHARINDEX > 0使用することがCHARINDEX > 0ます。


強い声明

画像


このSO投稿は、EFCチームの決定に疑問を投げかけています。


コードレンズは、 SqlServerContainsOptimizedTranslator 1つの場所( SqlServerCompositeMethodCallTranslator.cs)でのみ使用されることを示しています。 ビンゴ! このクラスはRelationalCompositeMethodCallTranslatorを継承し、名前から判断して、.NETメソッドの呼び出しをSQLクエリに変換します。これが必要なのです! このクラスを展開し、別のカスタムトランスレータをリストに追加するだけです。


翻訳者を書く


IMethodCallTranslatorIMethodCallTranslatorインターフェースを実装する必要があります。 Expression Translate(MethodCallExpression methodCallExpression)メソッドExpression Translate(MethodCallExpression methodCallExpression)実行する必要があるExpression Translate(MethodCallExpression methodCallExpression)は非常に簡単です。入力式が不明な場合はnullを返し、そうでない場合は式をSqlに変換します。
クラスは次のようになります。


 public class FreeTextTranslator : IMethodCallTranslator { private static readonly MethodInfo _methodInfo = typeof(StringExt).GetRuntimeMethod(nameof(StringExt.ContainsText), new[] {typeof(string), typeof(string)}); public Expression Translate(MethodCallExpression methodCallExpression) { if (methodCallExpression.Method != _methodInfo) return null; var patternExpression = methodCallExpression.Arguments[1]; var objectExpression = (ColumnExpression) methodCallExpression.Arguments[0]; var sqlExpression = new SqlFunctionExpression("CONTAINS", typeof(bool), new[] { objectExpression, patternExpression }); return sqlExpression; } } 

CustomSqlMethodCallTranslatorを使用して接続するだけです。


 public class CustomSqlMethodCallTranslator : SqlServerCompositeMethodCallTranslator { public CustomSqlMethodCallTranslator(RelationalCompositeMethodCallTranslatorDependencies dependencies) : base(dependencies) { // ReSharper disable once VirtualMemberCallInConstructor AddTranslators(new [] {new FreeTextTranslator() }); } } 

EFCのDI


EFCはDIパターンを最大限に活用しています。 ケストレルチームの影響を感じます(またはその逆)。 既にASP.NET Coreを使用している場合、EFCでのcurlの実装と解決を理解するのに問題はありません。 UseSqlServer拡張UseSqlServerは、ライブラリが機能するために必要な数十の依存関係をインストールします。 ソースはここにありますICompositeMethodCallTranslatorがあり、 ReplaceServiceヘルパーを使用して上書きします


 optionsBuilder.ReplaceService<ICompositeMethodCallTranslator, CustomSqlMethodCallTranslator>(); 

インストールして実行します。


 var textContains = dbContext.Posts.Where(x => x.Content.ContainsText("egg")).ToArray(); 

SQL生成の問題


リリース後、2つのニュースが見つかりました。 良いことは、カスタムトランスレーターがEFCによって正常に選択されたことです。 悪い-要求は間違っていました。


 SELECT [x].[Id], [x].[AuthorId], [x].[BlogId], [x].[Content], [x].[Created], [x].[Rating], [x].[Title] FROM [Posts] AS [x] WHERE CONTAINS([x].[Content], N'egg') = 1 

明らかに、式の中間ツリーを既製のクエリに変換する最終的なSQLジェネレーターは、SQL関数からの値を必要とします。 ただし、CONTAINSはSQLジェネレーターが注意を払わないブール値を返す述語です。 グーグルで、松葉杖を作成するための多くの失敗した試み、私はあきらめました。 SQL文字列をそのまま最終クエリに挿入するSqlFragmentExpressionを使用してみました。 不適切に追加されたジェネレーター= 1 。 寝る前に、プロジェクト#11316 githubにバグレポートを残しました。 そして、見よ、彼らは24時間以内に問題とその解決策の需要を教えてくれました。


問題と解決策


私の考えでは、SQLジェネレーターは戻り値がtrueであることを望んでいます。 この問題を解決するには、SqlVisitor'eでVisitBinaryをVisitUnaryに置き換える必要がありました。 CONTAINSは単項演算子です。 これが実装されたアイデアです。 類推して行動し、カスタムジェネレーターを作成し、コンテナーに接続して再起動します。


 public class FreeTextSqlGenerator : DefaultQuerySqlGenerator { internal FreeTextSqlGenerator(QuerySqlGeneratorDependencies dependencies, SelectExpression selectExpression) : base(dependencies, selectExpression) { } protected override Expression VisitBinary(BinaryExpression binaryExpression) { if (binaryExpression.Left is SqlFunctionExpression sqlFunctionExpression && sqlFunctionExpression.FunctionName == "CONTAINS") { Visit(binaryExpression.Left); return binaryExpression; } return base.VisitBinary(binaryExpression); } } 

すべてが機能し、正しいSQLが生成されます。 ContainsTextメソッドはさまざまな式に参加できますが、一般に、EFCの完全なメンバーです。


結論


建築的には、EFCは従来のEFをはるかに上回っています。 拡張することは問題ではありませんが、ソースで解決策を探す準備をしてください。 私にとって、これは新しいことを学ぶ主な方法の1つですが、多くの時間を要します。


プロジェクトのメンテナーは、あなたの質問に詳細な回答をする準備ができています。 バグを報告してから4日後に、別の〜20の問題が開かれていることに気付きました。 それらのほとんどは答えられました。


完成したコードはこちらです。 それを実行するには、Linuxコンテナ上の最新のVSとドッカー、またはフルテキスト検索を備えたSQL Serverが必要です。 残念ながら、localdbには言語サービスがなく、それらを接続することはできません。 インターネットのdockerファイルを使用しました。 dockerイメージのアセンブリと起動は、database-create.ps1ファイルにあります。


また、コマンドレットupdate-databaseを使用して移行を開始することを忘れないでください。



Source: https://habr.com/ru/post/J351556/


All Articles