14

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.

user990423
  • 1,397
  • 2
  • 12
  • 32
Roland Buergi
  • 1,157
  • 9
  • 23
  • Lists cannot be used as parameters so it kind of does what you suggest which is that it constructs a new query because your list can contain different elements each time you call it. So it is actually an SQL limitation. – Ron Deijkers Oct 15 '15 at 18:47
  • Not quite; as said, it starts parsing the expression tree from scratch each time. we have a query with a couple of joins which takes 5 secs to parse (with a few ms on sql server), that's why I am looking for a workaround. – Roland Buergi Oct 16 '15 at 07:05
  • 1
    @rolandHow big is this list? Also do you ahve to use contains? Problem could be depending on how long each entry is.......just how much he needs to compare. I had a similar problem with .StartsWith until I used StringComparison.Ordinal there which sped it up considerably ( compared to .Contains). Problem could be just that he needs to iterate through too big strings (to big in terms of it takes time). If you could change it to startswith and ordinal it should spead up considerably (but depends on your exact use case). – Thomas Oct 19 '15 at 05:58
  • 1
    Problem for contains is just.....sql servers are made for this type of search and are optimized for it whereas c# can't take it up with them in that area. Maybe it is better to restructure your sql to already search for these inside the sql? – Thomas Oct 19 '15 at 05:58
  • Take a look at this http://blog.codinghorror.com/compiled-or-bust/ – Eldho Oct 20 '15 at 05:26
  • @thomas: I am looking for solutions for small as well as for big lists. For big lists, there is the advice on the internet to use a separate table to store the input to the query; however, the problem is that in EF you cannot map types on the fly, and thus I don't think this is a viable option. I'd be happy to have a solution which works more or less as described in my question above with substituting a placeholder. – Roland Buergi Oct 20 '15 at 19:52
  • 2
    @Eldho: I am familiar with this article, and I speak about cases where really the parsing of the expression tree takes several seconds, i.e we will be bust without compilation. – Roland Buergi Oct 20 '15 at 20:04

1 Answers1

7

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));
Akash Kava
  • 39,066
  • 20
  • 121
  • 167
  • Thanks for this answer; it is unfortunately still not what I am looking for. As mentioned above, we have a lot of queries like this, and writing a lot of custom SQL / Views is not really the way to go as then we will screw up maintainability of the application. I wonder if there is any way to add a temporary type to EF. Idea: We could add a temporary table to store the list of Contains parameters and then introduce a temporary type to formulate the query. Does anyone know if EF allows to introduce temporary types? – Roland Buergi Oct 26 '15 at 09:36
  • Yes instead of XML or JSON, you could also use a Temporary Table. but it will add overhead to Database, it will increase number of active transactions, leading to poor performance by database engine. – Akash Kava Oct 26 '15 at 09:38
  • Yes, but can I add the temporary C# type to EF? I would need it to formulate the query, such as context.MyEntities.Where(x => context.Set(typeof(TemporaryType)).Contains(x.Id)). I wouldn't mind doing all the reflection needed in the background. – Roland Buergi Oct 26 '15 at 09:40
  • "... there is no way to precompile this query with IEnumerable.Contains" Sure, there is no way to precompile to a SQL command. But why couldn't EF store precompiled queries in some other form -- e.g., just introduce a dedicated data structure to store precompiled queries? – Myk Jan 24 '19 at 14:54