10

Firstly I tried ARITHABORT OFF on SSMS it's still less than 1 second.

I use EntityFrameWork: 6.1.3 and Azure Sql S1 tier (I will try with Tier 3 and let you know if something changes.)

I use EF Profiler to get generated sql from linq. I have queried all of linqs which I have shared, they all are less than 1 second on SSMS.

I have 3 million recods on AuditLog Table. One customer with ID 3 has 170K records the other customer with ID 35 has 125 records. I will minimize the code.

AuditLog Model:

 public class AuditLog
  {
    public long? CustomerId { get; set; }

    [ForeignKey("CustomerId")]
    public virtual CustomerSummary Customer { get; set; }

    [Required]
    [Index]
     public DateTime CreatedDate { get; set; }
  }

First query:

 if (customer != null)
    {
      var customerId = customer.Id;
      var result= Dbset.Where(x => x.CustomerId == customerId).OrderByDescending(x => x.CreatedDate).Skip(0).Take(25).ToList();
    }

if I try with customer who has 170k rows, it gives time out exception. If I try with customer who has 125 records, it's fine.

Second Query: It's same with first one I just include Customers.

if (customer != null)
   {
      var customerId = customer.Id;
      var result= Dbset.Where(x => x.CustomerId == customerId).OrderByDescending(x => x.CreatedDate).Skip(0).Take(25).Include(x => x.Customer).ToList();
    }

The result is opposite of first query. if I try with customer who has 170k rows, it's fine. If I try with customer who has 125 records,it gives timeout exception.

Third query: It's same with first query, but I match long? on where for customerId.

 if (customer != null)
    {
      long? customerId = customer.Id;
      var result= Dbset.Where(x => x.CustomerId == customerId).OrderByDescending(x => x.CreatedDate).Skip(0).Take(25).ToList();
    }

The result is opposite of first query. if I try with customer who has 170k rows, it's fine. If I try with customer who has 125 records,it gives timeout exception.

Fourth query: It's same with second query, but I match long? on where for customerId.

 if (customer != null)
    {
      long? customerId = customer.Id;
      var result= Dbset.Where(x => x.CustomerId == customerId).OrderByDescending(x => x.CreatedDate).Skip(0).Take(25).Include(x => x.Customer).ToList();
    }

The result is opposite of second query. if I try with customer who has 170k rows, it gives time out exception. If I try with customer who has 125 records, it's fine.

I'm really confused. Why inner join or changing match paramter to long? are changing results ? And why this all queries run under 1 sec on SSMS and give error on ef linq ?

Error:

{System.Data.SqlClient.SqlException (0x80131904): Timeout expired. The timeout period elapsed prior to completion of the operation or the server is not responding. ---> System.ComponentModel.Win32Exception (0x80004005): The wait operation timed out at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)

Update (19/04/2016):

After Ivan Stoev suggestion on comments.

Have you tried (just for the sake of test) using hardcoded 3 and 35 instead of customerId variable?

I didn't get any error and queries are fastest as on SSMS.

Update (20/04/2016): The real problem is Parameter Sniffing. When I included or changed parameter to nullable, actually I have created another queries and another query plans. I created some plans with customer who has 125 records, and the other ones with customer who has 170k records of these 4 queries. That's why I got different results.

Erkan Demirel
  • 4,302
  • 1
  • 25
  • 43
  • How are you comparing the results? Are you comparing against the SQL generated by EF or are you writing your own? – Josh Mein Apr 18 '16 at 15:59
  • Sql generated by EF. – Erkan Demirel Apr 18 '16 at 16:00
  • That is interesting. It may be useful to also provide what version of EF you are using. – Josh Mein Apr 18 '16 at 16:03
  • 1
    Have you tried (just for the sake of test) using hardcoded 4 and 35 instead of `customerId` variable? – Ivan Stoev Apr 18 '16 at 16:03
  • Run `DBCC FREEPROCCACHE` and then `sp_updatestats` and see if the problem remains. – Magnus Apr 18 '16 at 16:04
  • 1
    Parameter Sniffing? If this is SQLAzure you'll have to alter the table/objects to clear cache or run sp_recompile since FREEPROCCACHE is not available in sql azure. http://dba.stackexchange.com/questions/39689/how-can-i-remove-a-bad-execution-plan-from-azure-sql-database – David Oesterreich Apr 18 '16 at 16:14
  • i agree with @Magnus, try rebuild index and updatestats, and would please post generated sql query here. and also i prefer using projection in query(try just the columns you need). the last thing pass the paging in parameterized (insted skip(0).Take(25) -> skip(start).Take(length) ) – Mahdi Farhani Apr 19 '16 at 07:01
  • @IvanStoev It worked suprisingly I have edited the question also. Is it because of ef casting/converting parameters inside the linq ? – Erkan Demirel Apr 19 '16 at 07:37
  • I (as other commenters here) was trying to see if you are victim of so called "parameter sniffing" problem, which usually is the case when you hear "works fast in SSMS and slow in EF", and looks like this is indeed the case. – Ivan Stoev Apr 19 '16 at 07:42

1 Answers1

8

What you are experiencing is a result of so called Parameter Sniffing Problem. I don't know a simple general solution so far, so usually suggest a workaround by eliminating some of the SQL query parameters by manually binding constant values inside the expressions, like in EntityFramework LINQ query count fails but query returns result. How to optimize LINQ query?.

For your scenario, I would suggest the following custom extension method:

public static class QueryableExtensions
{
    public static IQueryable<T> WhereEquals<T, TValue>(this IQueryable<T> source, Expression<Func<T, TValue>> selector, TValue value)
    {
        var predicate = Expression.Lambda<Func<T, bool>>(
            Expression.Equal(selector.Body, Expression.Constant(value)),
            selector.Parameters);
        return source.Where(predicate);
    }
}

and then update your snippet like this

if (customer != null)
{
    var result= Dbset.WhereEquals(x => x.CustomerId.Value, customer.Id)
        .OrderByDescending(x => x.CreatedDate)
        .Skip(0).Take(25)
        .Include(x => x.Customer)
        .ToList();
}
Community
  • 1
  • 1
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • Hi Ivan, `var ListItems = (from pl in _context.Order where !pl.SentDateUTC.HasValue select pl).ToList();` is this case of Parameter Sniffing? Because above simple Linq failed with Timeout exception while query works. can you help me to resolve this issue? – Yogen Darji Oct 31 '17 at 11:41
  • @yogendarji Hi, your case is different, because the query does not use parameters. Basically you are searching for `pl.SentDateUTC == null`. Most likely it's doing full table scan. But shouldn't be taking so long, How many records do you have in the db table? – Ivan Stoev Oct 31 '17 at 12:01
  • I've around 150k records in the table. query plan suggest me to creating Non cluster Index though. Should I create index on that column? – Yogen Darji Oct 31 '17 at 12:03
  • 1
    @yogendarji What happens if you capture the SQL query (`var sql = (from pl in _context.Order where !pl.SentDateUTC.HasValue select pl).ToString();` and execute it directly in SSMS? – Ivan Stoev Oct 31 '17 at 12:05
  • 1
    Sorry about my first statement that query works fine directly on SSMS. just checked with above code and in SSMS also it is very slow.(Earlier I was checking on another env so thought it is working fine normally). Now thinking Index is only last option to get it work. Thanks for your help man! – Yogen Darji Oct 31 '17 at 12:11