Performance issues in .NET applications rarely come from the framework itself. In most real-world cases, they originate from inefficient database usage, poor async handling, excessive memory allocation, or missing production tuning.
Step 1: Measure First, Optimize Second
Why This Matters
Optimizing without data is guesswork. Before touching code, identify where time and resources are being spent.
What to Measure
-
- Slow APIs (response time > 500 ms)
- High memory usage
- Repeated database queries
- CPU-heavy methods
Real-World Tools
-
- Application Insights
- Visual Studio Diagnostic Tools
- dotnet-counters
- dotnet-trace
Step 2: Reduce Database Calls (Biggest Performance Gain)
Most production slowdowns are caused by unnecessary or repeated database access.
Common Mistake
Fetching full entities when only a few fields are needed.
Inefficient Query(Bad)
var users = context.Users.ToList();
Optimized Query (Projection Good Practice)
var users = context.Users
.Select(u => new { u.Id, u.Name })
.ToList();
Additional Improvements
-
- Avoid database calls inside loops
- Combine related queries
- Cache lookup data (states, roles, categories)
Step 3: Use Async/Await the Right Way
Async programming improves scalability, not execution speed.
Blocking Async Calls(Avoid)
var data = GetUsersAsync().Result;
Proper Async Usage(Use)
var data = await GetUsersAsync();
Where Async Helps Most
-
- Database calls
- File I/O
- External HTTP APIs
Async will not improve CPU-bound logic.
Step 4: Optimize Entity Framework for Production
Use No-Tracking for Read-Only Queries
var users = context.Users
.AsNoTracking()
.ToList();
Avoid Lazy Loading in Loops
// N+1 query problem
foreach (var user in users)
{
var roleCount = user.Roles.Count();
}
Better Approach
var users = context.Users
.Include(u => u.Roles)
.ToList();
Step 5: Implement Smart Caching
If data is read frequently and changes rarely, it should be cached.
Example: In-Memory Caching
| public async Task<List<State>> GetStatesAsync()
{ return await _cache.GetOrCreateAsync(“states”, async entry => { entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(6); return await _context.States.AsNoTracking().ToListAsync(); }); } |
When to Use Redis
-
- Multiple servers
- High traffic
- Shared session or config data
Step 6: Control Memory Usage
Common Memory Issues
-
- Loading large files into memory
- Not disposing streams
- Excessive object creation
Memory-Heavy File Handling
var bytes = File.ReadAllBytes(path);
Stream-Based Approach
using var stream = new FileStream(path, FileMode.Open);
Result
Lower GC pressure and improved stability.
Step 7: Optimize API Response Size
Best Practices
-
- Return only required fields
- Implement pagination
- Enable response compression
Enable GZip Compression
services.AddResponseCompression();
app.UseResponseCompression();
Why This Helps
-
- Faster API responses
- Reduced bandwidth usage
- Better mobile performance
Step 8: Improve Application Startup Time
Startup delays hurt scalability, especially in cloud environments.
Practical Tips
-
- Avoid heavy logic in Program.cs
- Lazy-load background services
- Precompile Razor views
Result
Faster cold starts and smoother deployments.
Step 9: Tune Hosting & Server Configuration
IIS / Kestrel Optimizations
-
- Enable HTTP/2
- Remove unused middleware
- Limit request body size where possible
Advanced (Use Carefully)
ThreadPool.SetMinThreads(200, 200);
Apply only after profiling and load testing.
