1

I recently determined that there are no significant performance gains from using a Dependency Injected DbContext in .NET Core and using async await calls as opposed to creating a new DbContext every time I want to access the DB.

But now I need to know why.

I did a much more granular test with System.Diagnostics.Stopwatch in my .NET Core 1.1 API services (which the controller is calling) in which I ran the stopwatch only when accessing the DB. The results were surprising.

When using the standard Dependency Injected context and async/await calls:

var task1 = _InjectedDbContext.Table1.FirstOrDefaultAsync(p => p.Id == SomeId);
var task2 = _InjectedDbContext.Table2.Where(u => u.AnotherId == SomeOtherId).ToListAsync();

(var result1, var result2) = await (task1, task2).WhenAll();

each DbContext query took significantly less than 100 ms.

However, when using this method:

using (var context = new DbContext(_InjectedContextOptions.Options))
{
    var task1 = context.Table1.FirstOrDefaultAsync(p => p.Id == SomeId);
    var task2 = context.Table2.Where(u => u.AnotherId == SomeOtherId).ToListAsync();

    (var result1, var result2) = await (task1, task2).WhenAll();
}

Each DbContext query took anywhere from 100-230 ms.

FYI, here is my code for the DI setup in Startup.cs ConfigureServices:

var connection = Configuration.GetConnectionString("mydb");
services.AddDbContext<MyDbContext>(options => options.UseSqlServer(connection));

And here is my code for providing the DbContextOptions as a singleton whenever I create a new DbContext:

var dbContextOptions = new DbContextOptionsBuilder<MyDbContext>();        
dbContextOptions.UseSqlServer(Configuration.GetConnectionString("MyDb"));
services.AddSingleton(dbContextOptions);

I also determined that the lag is not caused by simply the creation of the DbContext in the using statement (which is a very fast operation). What is going on here? Is it trying to re-connect to the DB every time or something?

starmandeluxe
  • 2,443
  • 3
  • 27
  • 44
  • How did you register your DbContext? Because the DI might hold a copy of the DbContext in memory for the duration of the application. the request or one call bases on the scope you registered the DbContext with. This would explain the performance gain. – rickvdbosch May 29 '17 at 07:56
  • Hi, good question. I updated my question with code for the DI in both cases. The lifetime is at the default: https://stackoverflow.com/questions/37507691/entity-framework-core-service-default-lifetime – starmandeluxe May 29 '17 at 07:59
  • @RickvandenBosch: Default registration for AddDbContext is scoped, so its resolved per request, so that shouldn't be it. Do you by chance create a huge number of DbContext instances during a single request? Typically DbContext will recycle connections, but if you create a big number of connections that may be the reason for it – Tseng May 29 '17 at 08:18
  • @Tseng For this one API call I am testing, there are exactly 6 creations of DbContext, no more, no less. I wouldn't expect that to cause this. – starmandeluxe May 29 '17 at 08:25
  • Why do you care? What are you optimizing for? – Mardoxx May 29 '17 at 09:01
  • I'm not sure how that's relevant to me being able to ask the question, but FWIW, I want to know which strategy is better to use "at scale". I am obviously trying to optimize for speed here, but whether it matters if the DB access is faster, or the entire API roundtrip is faster, I am not sure yet. – starmandeluxe May 29 '17 at 09:05
  • 1
    I'm surprised those queries are working also - https://stackoverflow.com/questions/41749896/ef-6-how-to-correctly-perform-parallel-queries – Mardoxx May 29 '17 at 09:27
  • Regardless, how are you timing your code? – Mardoxx May 29 '17 at 09:30
  • @Mardoxx I see you're paying attention ;) You are absolutely correct: the WhenAll code does fail randomly when I use the new dbcontext strategy, but it doesn't seem to have that problem with a DI'ed context (not sure why this is, I'd like to know). I am fully aware that I should be awaiting it, but that's out of the scope of this question. – starmandeluxe May 29 '17 at 09:51
  • As for how I am timing, it is very simple : I create a new StopWatch object and start it every time I am about to access the context, and stop it after WhenAll. This doesn't change with either strategy. – starmandeluxe May 29 '17 at 09:53
  • It does if you are exclusing the construction of the DbContext surely? Post it anyway :) – Mardoxx May 29 '17 at 10:08
  • @Mardoxx I actually put the timer both before and after the creation of the DbContext to see if there was a difference. It turned out it was negligible. The actual instantiation of the DbContext is almost costless performance-wise, which is why I think the bottleneck might be something to do with either WhenAll or connecting to the DB itself. – starmandeluxe May 29 '17 at 11:55
  • I'm also perplexed, was convinced EF Core was 3x slower than EF 6 until I registered the DBContextOptions as a singleton and now it's neck & neck. – James White Feb 26 '19 at 16:26

1 Answers1

0

You're using the AddDbContext method, for the DbContext you use in the first scenario, which adds the DbContext to services as Scoped (if I'm not mistaking). The DbContext is probably initialized as soon as the service is added because of optimization (not sure here). For the second case you're creating a new DbContext. Apart from the DbContext creation there's also some other stuff that needs to be done.

Taken from this post, here are some tips to 'warm-up' your context:

  1. Using a cached DB model store
  2. Generate pre-compiled views
  3. Generate pre-compiled version of entityframework using n-gen to avoid jitting

The tips above indicate there's more to using a DbContext than 'just' newing one and start querying.

rickvdbosch
  • 14,105
  • 2
  • 40
  • 53
  • I agree with your first point about the initialization with DI. But I am just not sure about these optimizations: wouldn't that help performance with BOTH methods that I've presented here, not only the new context method? I am more interested in explaining the difference between the two, not general tips to increase EF performance. Also, I did benchmark ONLY the creation of the DbContext and it is a very insignificant performance hit... – starmandeluxe May 29 '17 at 08:23
  • Hi @starmandeluxe, as I said earlier: I can imagine there's some optimization in the AddDbContext method which causes the DI scenario to be faster. The tips are only there to show there is room for improving performance, these might already be implemented in the AddDbContext method, causing it to be faster than newing one up yourself. – rickvdbosch May 29 '17 at 09:16
  • @RickvandenBosch I would think the problem lies in how things are being timed. – Mardoxx May 29 '17 at 09:31