111

I'm seeing some really strange perf related to a very simple query using Entity Framework Code-First with .NET framework version 4. The LINQ2Entities query looks like this:

 context.MyTables.Where(m => m.SomeStringProp == stringVar);

This takes over 3000 milliseconds to execute. The generated SQL looks very simple:

 SELECT [Extent1].[ID], [Extent1].[SomeStringProp], [Extent1].[SomeOtherProp],
 ...
 FROM [MyTable] as [Extent1]
 WHERE [Extent1].[SomeStringProp] = '1234567890'

This query runs almost instantaneously when run through Management Studio. When I change the C# code to use the SqlQuery function, it runs in 5-10 milliseconds:

 context.MyTables.SqlQuery("SELECT [Extent1].[ID] ... WHERE [Extent1].[SomeStringProp] = @param", stringVar);

So, exact same SQL, the resulting entities are change-tracked in both cases, but wild perf difference between the two. What gives?

Michael Sandler
  • 1,231
  • 3
  • 21
  • 29
Brian Sullivan
  • 26,473
  • 22
  • 75
  • 90
  • 2
    I expect you're seeing initialization delays - probably view compilation. See MSDN: [`Performance Considerations for Entity Framework 5`](http://msdn.microsoft.com/en-us/data/hh949853.aspx) – Nick Butler Apr 02 '13 at 15:23
  • I've tried pre-generating views, and it doesn't seem to help. Also, ran another EF query before the slow one to rule out initialization stuff. New query ran quickly, the slow one still ran slowly, even though context warm-up happened during the first query. – Brian Sullivan Apr 02 '13 at 15:46
  • 1
    @marc_s - No, SqlQuery will return a fully-materialized and change-tracked entity instance. See http://msdn.microsoft.com/en-us/library/system.data.entity.dbset.sqlquery(v=vs.103).aspx – Brian Sullivan Apr 02 '13 at 16:24
  • Is the generated SQL for your EF query actually inlining the parameter value, or using a parameter? This shouldn't affect query speed for an individual query, but could cause queryplan bloat in the server over time. – Jim Wooley Apr 02 '13 at 17:04
  • Have you tried running the same query twice/multiple times? How long did it take when running the second time? Have you tried this on .NET Framework 4.5 - there are some EF related perf improvements in .NET Framework 4.5 that could help. – Pawel Apr 02 '13 at 17:45
  • @JimWooley - No, EF is inlining the string value. I suppose I could try hard-coding it into my SqlQuery call to see if I get similar (bad) perf, but like you said, I doubt that's it. – Brian Sullivan Apr 02 '13 at 17:58
  • EF detects if the variable is a constant or user entered and inlines or parameterizes it appropriately. I doubt that's the issue with your query, but try making it a changeable variable and retesting. – Jim Wooley Apr 02 '13 at 18:53
  • @Kiquenet - There's really not much more source code to show other than what's in the question and in the answer. – Brian Sullivan Apr 09 '13 at 13:46

6 Answers6

119

Found it. It turns out it's an issue of SQL data types. The SomeStringProp column in the database was a varchar, but EF assumes that .NET string types are nvarchars. The resulting translation process during the query for the DB to do the comparison is what takes a long time. I think EF Prof was leading me astray a bit here, a more accurate representation of the query being run would be the following:

 SELECT [Extent1].[ID], [Extent1].[SomeStringProp], [Extent1].[SomeOtherProp],
 ...
 FROM [MyTable] as [Extent1]
 WHERE [Extent1].[SomeStringProp] = N'1234567890'

So the resulting fix is to annotate the code-first model, indicating the correct SQL data type:

public class MyTable
{
    ...

    [Column(TypeName="varchar")]
    public string SomeStringProp { get; set; }

    ...
}
Brian Sullivan
  • 26,473
  • 22
  • 75
  • 90
  • 1
    Nice investigation. Your query was suffering from "implicit conversion", as it is explained here: http://www.brentozar.com/archive/2012/07/identifying-correcting-sql-server-implicit-conversion/ – Jaime Oct 15 '14 at 08:14
  • Saved me a few hours of debugging. This was exactly the problem. – Cody Jul 23 '15 at 00:40
  • 1
    In my case, I'm using EDMX with a legacy database, which uses `varchar` for everything, and indeed this was the problem. I wonder if I can make an EDMX to consider varchar for everything string column. – Alisson Reinaldo Silva Jun 30 '17 at 12:45
  • 1
    Great finding man. but @Jaime what we should do for database first approach as everything(e.g data annotation on db Models) wipes away after updating EF model from database. – Nauman Khan Oct 30 '17 at 13:12
  • Setting this as my home page for a while so I can relive the excitement of finding such a great answer again for a while. Thank you!!! – OJisBad Aug 18 '20 at 20:33
  • Thank you! This saves me lots of hours of work and analysis :) – fgpx78 Dec 23 '21 at 08:19
53

The reason of slowing down my EF queries was comparing not nullable scalars with nullable scalars:

long? userId = 10; // nullable scalar

db.Table<Document>().Where(x => x.User.Id == userId).ToList() // or userId.Value
                                ^^^^^^^^^    ^^^^^^
                                Type: long   Type: long?

That query took 35 seconds. But a tiny refactoring like the following:

long? userId = 10;
long userIdValue = userId.Value;

db.Table<Document>().Where(x => x.User.Id == userIdValue).ToList()
                                ^^^^^^^^^    ^^^^^^^^^^^
                                Type: long   Type: long

gives incredible results: it took only 50ms to complete. It looks like a bug in EF.

cryss
  • 3,900
  • 1
  • 27
  • 34
  • 15
    This is so weird – Daniel Cardenas Aug 03 '16 at 03:41
  • 1
    OMG. This can apparently also happen when using interfaces IUserId.Id was causing the problem with me, but first mapping Id to an integer works... do I have to check now all queries in my 100.000 lines application? – Dirk Boer Jan 29 '19 at 17:04
  • has this bug been reported? It's still in the latest version 6.2.0 – Dirk Boer Jan 29 '19 at 17:06
  • 2
    The same issue is also in EF Core. Thanks for finding this! – Yannickv Jan 31 '20 at 09:23
  • One other suggestion is to process the variable before putting into LINQ expression. Otherwise the generated sql will be much longer and slower. I experienced when having Trim() and ToLower() inside LINQ expression that bugs me up. – samheihey Jun 22 '20 at 06:51
12

If you're using the fluent mapping, you can use IsUnicode(false) as part of the configuration to get the same effect -

http://msdn.microsoft.com/en-us/data/jj591617.aspx#1.9

http://msdn.microsoft.com/en-us/library/gg696416%28v=vs.103%29.aspx

Matt
  • 315
  • 4
  • 13
4

I had the same problem (the query is fast when executed from SQL manager) but when executed from EF the timeout expires.

Turns out that the entity (which was was created from the view) had wrong entity keys. So the entity had duplicate rows with the same keys, and I guess it had to do grouping on the background.

3

I also came across this with a complex ef query. One fix for me which reduced a 6 second ef query to the sub second sql query it generated was to turn off lazy loading.

To find this setting (ef 6) go to the .edmx file and look in the Properties -> Code generation -> Lazy Loading Enabled. Set to false.

Massive improvement in performance for me.

3

I had this problem as well. It turns out the culprit in my case was SQL-Server parameter sniffing.

The first clue that my problem was in fact due to parameter sniffing was that running the query with "set arithabort off" or "set arithabort on" yielded drastically different execution times in Management Studio. This is because ADO.NET by default uses "set arithabort off" and Management Studio defaults to "set arithabort on". The query plan cache keeps different plans depending on this parameter.

I disabled query plan caching for the query, with the solution you can find here.

Oskar Sjöberg
  • 2,508
  • 25
  • 30