简体   繁体   中英

Entity Framework: Precompiled Query for Enumerable.Contains

Entity Framework 5+ is supposed to precompile all queries. However, for queries such as

List<Guid> ids;
var entities = context.MyEntities.Where(x => ids.Contains(x.Id)).ToArray();

Entity Framework cannot precompile the query, and depending on the complexity of the overall query, the parsing of the expression tree to SQL can consume several seconds. Has anyone found a workaround to get a precompiled query anyway? I do not really understand why it would be so hard; of course it is difficult to do with paramters, since the number of elements can differ, but it would be good enough to have SQL like

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

and then to substitute the placeholder with the actual list elements. Of course, it is not as nice as passing parameters, but it would be by far better than waiting for seconds for parsing the entire expression tree over and over.

You have to first understand how "IN" operator works in parameterized SQL query.

SELECT A FOM B WHERE C IN @p 

does not work, SQL command parameter does not accept ARRAY as a parameter value, instead the query is translated to

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

This query has variable number of parameters and this the reason, there is no way to precompile this query with IEnumerable.Contains .

The only other alternative (long long way) is to use Xml or Json (Coming up in Sql 2016).

Save your IEnumerable as xml.

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

And you can then define a VIEW with parameters as

SELECT A FROM B WHERE C IN (SELECT INT FROM Xml(@P1))

And you can use this View, however there are more challenges in EF to how to fire this query, but this query can be precompiled as it has only one parameter.

Custom SQL for Performance Hack

For pretty simple query like,

List<Guid> ids;
var entities = context.MyEntities.Where(x => ids.Contains(x.Id)).ToArray();

I could simply use a custom SQL and fire,

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());

Temporary Table

You can also use a temporary table, but this increases number of active transactions in your database.

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 SID=@sid,
     new SqlCommandParameter("@sid", sid));

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM