Should We Use using or Not?
The Real Story Behind IDisposable in .NET and EF Core
Almost every .NET developer has faced this question at least once:
“Should I wrap every database access in a using statement?”
This question usually comes from fear—fear of memory leaks, connection leaks, or errors such as Connection Pool Exhausted. As a result, some developers place a using statement around almost every object they create with new.
But the reality is that in modern .NET, things are not that simple.
If you’re working with Entity Framework Core and Dependency Injection, in many cases you don’t need to be responsible for releasing resources yourself.
The important point is understanding when you should take control and when you should trust the framework.
First, You Need to Understand Two Different Worlds
To understand IDisposable, you first need to recognize that your application works with two completely different kinds of resources.
Imagine your application is a company.
On your desk, you have a pile of paper documents.
When you’re finished with them, you simply throw them into the trash.
You don’t need to worry about them anymore.
Every so often, the company’s cleaning staff comes by and collects all the papers.
That’s exactly what the Garbage Collector does.
Managed memory is cleaned up automatically.
Now imagine that instead of paper, there’s a key to a bank vault sitting on your desk.
If you leave the office at the end of the day without returning the key, the cleaning staff can’t take it back to the bank.
The vault remains occupied.
This is exactly what happens with external resources.
Files, network connections, sockets, database connections, and operating system handles are not released by the Garbage Collector.
For those resources, we have to explicitly say:
Dispose();
Why Does IDisposable Exist?
IDisposable is probably one of the simplest interfaces in .NET.
public interface IDisposable
{
void Dispose();
}
Yet behind this single method lies the responsibility of releasing valuable system resources.
Whenever an object communicates with the outside world, there’s a good chance it’s disposable.
For example:
FileStream
NetworkStream
SqlConnection
DbContext
HttpResponseMessage
Socket
Bitmap
Graphics
These objects don’t just consume memory—they also hold operating system resources.
What Does using Actually Do?
Many developers think using is some kind of magic feature.
In reality, the compiler simply transforms it into a try/finally block.
This guarantees that Dispose() will be called even if an exception occurs.
Today there are two common styles.
The classic approach:
using (var stream = new FileStream(...))
{
// Work
}
And the newer syntax introduced in C# 8:
using var stream = new FileStream(...);
// Work
Both achieve exactly the same goal.
The only difference is readability and scope.
A Disposed Object Is No Longer Alive
One of the fundamental contracts in .NET is that once an object has been disposed, it should no longer be used.
If you call one of its methods again, you’ll typically get:
ObjectDisposedException
You can think of disposing an object like permanently shutting down a machine.
It’s not meant to be turned back on.
Why Don’t We Use using Around SaveChanges() in EF Core?
This is where most misunderstandings begin.
Imagine walking into a supermarket.
First, you grab a shopping cart.
Then you fill it with items.
Finally, you go to the checkout and pay.
In this analogy:
The DbContext is the shopping cart.
Queries, Add, and Update operations are collecting items.
SaveChanges is paying at the checkout.
Dispose is returning the shopping cart.
Now imagine returning the cart after picking up every single item, then grabbing a new one.
That’s exactly what happens if you dispose the context after every operation.
It creates unnecessary overhead without providing any benefit.
The Real Role of Dependency Injection
In ASP.NET Core, there’s an important rule:
Whoever creates an object is responsible for destroying it.
When you write:
builder.Services.AddDbContext<AppDbContext>();
You are no longer the owner of the DbContext.
The container is.
Its lifecycle looks roughly like this:
At the beginning of each request, the container creates a DbContext.
It injects it into your repository or service.
You simply use it.
At the end of the request, the container automatically calls Dispose().
All of this happens without you writing a single extra line of code.
That’s why if you receive a DbContext through constructor injection, you generally should not dispose it yourself.
So, When Is using Necessary?
If you create a disposable object with new, you’re responsible for releasing it.
For example, in a Console application or a Background Worker:
using var context = new AppDbContext(...);
Here, there is no container taking care of your objects.
But if the object comes from Dependency Injection, let the container do its job.
Is It Always Wrong Not to Dispose?
Sometimes yes, but not always.
For example, classes like:
MemoryStream
StringReader
In many scenarios, failing to dispose them won’t cause a serious performance problem.
Even so, if an object implements IDisposable and you own it, it’s still a good practice to dispose it.
Not only to release resources, but also to reduce pressure on the Garbage Collector.
A Few Simple Rules to Remember
If you created the object yourself, you’re responsible for disposing it.
If the object came from Dependency Injection, don’t dispose it.
Keep a DbContext alive for a Unit of Work, not for every individual query.
Use using var for better readability, but make sure its scope matches your intent.
Not every disposable object is dangerous, but every external resource deserves proper management.
Conclusion
Years ago, writing using was almost an automatic habit.
Modern ASP.NET Core architecture has removed much of that responsibility from the developer.
A senior engineer isn’t someone who writes a using statement for every object.
A senior engineer understands resource ownership.
They know who created an object.
They know who is responsible for destroying it.
And most importantly, they know when it’s appropriate to trust the infrastructure.
The next time you’re about to write a using statement around a DbContext, ask yourself one simple question:
“Who created this object?”
If you know the answer to that question, you’ll almost always know whether using is the right choice.




