2015-10-15 13 views
12

Entity Framework 5+はすべてのクエリをプリコンパイルすることになっています。しかし、このようEntity Framework:Enumerable.Containsのプリコンパイル済みクエリ

List<Guid> ids; 
var entities = context.MyEntities.Where(x => ids.Contains(x.Id)).ToArray(); 
としてクエリ

ためにEntity Frameworkのクエリをプリコンパイルすることができず、全体的なクエリの複雑さに応じて、SQLの式ツリーの解析は数秒を消費することができます。とにかく誰もがプリコンパイルされたクエリを取得する回避策を見つけましたか?どうしてそんなに難しいのか分かりません。もちろん要素の数が異なることがあるため、偶然にして行うことは困難であるが、それは

SELECT a, b, c from MyEntities 
WHERE c in __PLACEHOLDER__ 

のようなSQLを持つようにして、実際のリスト要素でプレースホルダを代用するのに十分な良いでしょう。もちろん、パラメータを渡すほどうまくはありませんが、式ツリー全体を何度も何度も構文解析するのを待つよりもはるかに良いでしょう。

+0

リストをパラメータとして使用することはできません。リストは、呼び出すたびに異なる要素が含まれている可能性があるため、新しいクエリを作成することを示唆しています。したがって、実際にはSQLの制限です。 –

+0

前述のように、毎回エクスプレッションツリーを最初から解析し始めます。私たちはいくつかの結合(SQLサーバー上で数ミリ秒)を解析するために5秒かかるクエリを持っています。そのため、私は回避策を探しています。 – Roland

+1

@rolandHowこのリストは大きいですか?また、あなたは含まれて使用するためにahveですか?問題は、各エントリーの長さによって決まります。どれくらい比較する必要があるのでしょうか。私はStringComparison.Ordinalを使用してそれをかなりスピードアップした(.Containsと比較して).Startsと同様の問題を抱えていました。問題は、大きすぎる文字列を繰り返し処理する必要があるということだけです(時間がかかります)。それをstartswithとordinalに変更することができれば、それはかなり上昇するはずです(しかし、あなたの正確なユースケースに依存します)。 – Thomas

答えて

6

まず、パラメータ化されたSQLクエリで "IN"演算子がどのように機能するかを理解する必要があります。

SQLコマンドのパラメータが代わりにクエリが

SELECT A FROM B WHERE C IN (@p1, @p2, @p3 ... etc) 

このクエリは、可変パラメータの数と、このような理由がありましたに変換され、パラメータ値としてARRAYを受け入れていない、動作しません。このクエリをIEnumerable.Containsでプリコンパイルする方法はありません。

XmlまたはJson(Sql 2016で登場)を使用する唯一の代替方法(長い長い方法)があります。

xmlとしてIEnumerableを保存します。

[10,20,20,50] can be translated to 
<data> 
    <int value="10"/> 
    <int value="20"/> 
    <int value="20"/> 
    <int value="50"/> 
</data> 

。しかし、あなたはその後、C IN(XMLからINT(@のP1)を選択)

B FROM

SELECT AなどのパラメータでVIEWを定義することができますし、このビューを使用することができますこの問合せをどのように起動するかについてEFにはより多くの課題がありますが、この問合せはパラメータが1つのみであるため事前コンパイルできます。

カスタムSQLパフォーマンスハックのような非常に単純なクエリの場合

List<Guid> ids; 
var entities = context.MyEntities.Where(x => ids.Contains(x.Id)).ToArray(); 
私は単純にカスタムSQLや火を使うことができ

var parameterList = ids.Select( 
    (x,i)=> new SqlCommandParameter(
     "@p"+i, x)); 

var pnames = String.Join(",", parameterList.Select(x=> x.ParameterName)); 

var entities = 
    context.SqlQuery<MyEntity>(
     "SELECT * FROM TABLE WHERE Id in (" + pnames + ")", 
     parameterList.ToArray()); 

一時表用

一時テーブルを使用することもできますが、データベース内のアクティブなトランザクションの数が増えます。

Guid sid = Guid.NewGuid(); 
foreach(var p in ids){ 
    db.TempIDs.Add(new TempID{ SID = sid, Value = p }); 
} 
db.SaveChanges(); 

var qIDs = db.TempIDs.Where(x=> x.SID == sid); 

var myEntities db.MyEntities.Where(x => qIDs.Any(q.Value == x.Id)); 

// delete all TempIDs... 
db.SqlQuery("DELETE FROM TempIDs WHERE [email protected], 
    new SqlCommandParameter("@sid", sid)); 
+0

この回答に感謝します。残念ながらまだ私が探しているものではありません。上記のように、我々はこのような多くのクエリを持っており、多くのカスタムSQL /ビューを記述することは、実際にアプリケーションのメンテナンス性を台無しにする方法ではありません。一時的な型をEFに追加する方法があるのだろうかと思います。アイデア:Containsパラメータのリストを格納する一時テーブルを追加し、一時的な型を導入してクエリを公式化することができます。 EFが一時的なタイプを導入できるかどうかは誰にも分かりますか? – Roland

+0

XMLまたはJSONの代わりに、はい、一時表を使用することもできます。データベースにオーバーヘッドが加わると、アクティブなトランザクションの数が増え、データベースエンジンのパフォーマンスが低下します。 –

+0

はい、一時的なC#タイプをEFに追加できますか? context.MyEntities.Where(x => context.Set(typeof(TemporaryType))。Contains(x.Id))などのクエリを定式化する必要があります。私はバックグラウンドで必要なすべてのリフレクションをしても構いません。 – Roland

関連する問題