-
Notifications
You must be signed in to change notification settings - Fork 3.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Include or Where .AsNotracking queries seems to perform worse in EF Core than leaving the .AsNotracking property out. #14366
Comments
@HjalteParner There are a couple dimensions beyond tracking/no-tracking here, so I updated your test code to separate them out more--see below. The dimensions I split out are:
Here's what I see:
Observations:
We'll look at this in triage, but from what I see the results look basically expected. public class Program
{
public static void Main()
{
//using (var context = new BlogContext())
//{
// Console.WriteLine("Seeding...");
// context.ChangeTracker.AutoDetectChangesEnabled = false;
// context.Database.EnsureDeleted();
// context.Database.EnsureCreated();
// for (int i = 0; i < 100000; i++)
// {
// var blog = new Blog {Posts = new List<Post>()};
// for (var j = 0; j < 10; j++)
// {
// blog.Posts.Add(new Post());
// }
// context.Add(blog);
// }
// context.SaveChanges();
//}
Console.WriteLine("Running once to prime the query cache...");
TimeQueries(1, false);
Console.WriteLine("Running with posts already tracked...");
TimeQueries(1, true);
Console.WriteLine("Running with posts not tracked...");
TimeQueries(1, false);
}
private static void TimeQueries(int iterations, bool postsLoaded)
{
foreach (TestCase testCase in Enum.GetValues(typeof(TestCase)))
{
using (var context = new BlogContext())
{
context.ChangeTracker.AutoDetectChangesEnabled = false;
List<Post> posts;
if (postsLoaded)
{
posts = context.Posts.ToList();
}
var timer = Stopwatch.StartNew();
for (var i = 0; i < iterations; i++)
{
switch (testCase)
{
case TestCase.ToList:
posts = context.Posts.ToList();
break;
case TestCase.NoTrackingToList:
posts = context.Posts.AsNoTracking().ToList();
break;
case TestCase.Where:
posts = context.Posts.Where(p => p.Blog.BlogID > 100).ToList();
break;
case TestCase.NoTrackingWhere:
posts = context.Posts.Where(p => p.Blog.BlogID > 100).AsNoTracking().ToList();
break;
case TestCase.Include:
posts = context.Posts.Include(p => p.Blog).ToList();
break;
case TestCase.NoTrackingInclude:
posts = context.Posts.Include(p => p.Blog).AsNoTracking().ToList();
break;
case TestCase.WhereInclude:
posts = context.Posts.Include(p => p.Blog).Where(p => p.Blog.BlogID > 100).ToList();
break;
case TestCase.NoTrackingWhereInclude:
posts = context.Posts.Include(p => p.Blog).Where(p => p.Blog.BlogID > 100).AsNoTracking().ToList();
break;
}
}
timer.Stop();
Console.WriteLine($"Test case {testCase} ran {iterations} iterations in {timer.ElapsedMilliseconds}ms");
}
}
}
}
public enum TestCase
{
ToList,
NoTrackingToList,
Where,
NoTrackingWhere,
Include,
NoTrackingInclude,
WhereInclude,
NoTrackingWhereInclude
}
public class Blog
{
public int BlogID { get; set; }
public List<Post> Posts { get; set; }
}
public class Post
{
public int PostID { get; set; }
public int BlogID { get; set; }
public Blog Blog { get; set; }
}
public class BlogContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Test;ConnectRetryCount=0");
}
} |
@ajcvickers Thanks for your comprehensive answer and your refactoring of my test code, which is much neater now. You are correct that my issue has more to do with the Include and Where clause than Tracking vs. No Tracking and in addition even more to do with the performance comparison between EF Core vs. EF6 so the title should probably be changed to reflect this, if you agree that there is an issue with EF Core. Unfortunately having already tracked entities is not possible for me as my production database is huge compared to the test database presented here - which is why I need the Where clause I have made a test using your refactored code on my computer using both EF6 and EF Core. Below is my results: EF6 Running with posts already tracked... Running with posts not tracked... EF Core Running with posts already tracked... Running with posts not tracked... From above I gather:
Last but not least!
|
@HjalteParner Thanks for the additional information. This does indeed look worrying; we will discuss. /cc @divega |
Triage: We need to investigate this further for 3.0. However, this should happen after the major query changes are merged. |
I am also experiencing an increase of anywhere from 20%-200% in execution time when adding .AsNoTracking() to my query when using EFCore 2.2.4. While my query is much more complex than the one above, it does include multiple joins, includes, and where clauses. This was tested using BenchmarkDotNet with warmup enabled and 20 iterations of the test run per method against a SQL database without any possible external factors affecting it. I ran the test in various configurations and got the same consistent result of AsNoTracking slowing things down appreciably.
|
Looking at this, I modified @ajcvickers code to use the In Memory database and DI so I could use the Db Context Pooling and remove localdb from the equation. My results are here
There probably should be a warning added to https://docs.microsoft.com/en-us/ef/core/querying/tracking about this defect |
@ajcvickers - for those of us who might have found this question and are concerned enabling AsNoTracking for all queries which don't need tracking, by default - is this only an issue in specific cases? Does it still exist in ef core 3.1.1? thx for the insights |
@pgrm We haven't root-caused the issue yet. It's certainly only an issue in specific cases. If you're concerned about perf here, then I think you'll need to do perf testing on your application. |
For what is worth, in my case for two different test scenarios average query duration between tracking and no-tracking is:
It seems that no-tracking is doing a better job, even though the numbers are not significant in my opinion. It also seems, that as @ajcvickers mentioned it is case-specific and not general. |
I've just hit a query (in 6.0.7) that performs significantly worse (~5x slower) under By changing |
Any updates on it? |
This issue is in the Backlog milestone. This means that it is not planned for the next release (EF Core 8.0). We will re-assess the backlog following the this release and consider this item at that time. However, keep in mind that there are many other high priority features with which it will be competing for resources. Make sure to vote (👍) for this issue if it is important to you. |
Include or Where .AsNotracking queries seems to perform worse in EF Core than leaving the .AsNotracking property out. This was not the case in EF 6.
So while DbSet.AsNoTracking.ToList() is slightly better or on par with DbSet.AsNoTracking.ToList()
then
performance a lot worse than
respectively.
Steps to reproduce
The text was updated successfully, but these errors were encountered: