MVC+EF6.1应用中重启IIS可消除的OptimisticConcurrencyException问题
Hey there, let’s tackle this frustrating issue you’re seeing—two massive bursts of OptimisticConcurrencyException with the "0 rows affected" message, fixed temporarily by restarting IIS. Given your setup (MVC, EF6.1, two load-balanced IIS servers, ~100 concurrent users), here’s a breakdown of the most likely causes and actionable fixes:
Root Cause Hypothesis (Why Restarting IIS Fixes It)
The biggest red flag here is that restarting IIS resolves the issue temporarily. This almost always points to long-lived or shared DbContext instances. EF’s DbContext is designed to be short-lived (per-request, ideally)—if you’re using a static/singleton DbContext or reusing instances across multiple requests, the entity tracking state gets corrupted across concurrent users and servers. When one request updates a record, another request’s stale DbContext tries to update the same record with outdated entity data, triggering the concurrency exception. Restarting IIS wipes out these shared, corrupted DbContext instances, hence the temporary fix.
Actionable Fixes
1. Fix DbContext Lifetime (Critical First Step)
First, audit how you’re instantiating your DbContext:
- Remove any static/singleton DbContext instances (e.g.,
public static MyDbContext Context = new MyDbContext();). These are toxic in load-balanced, high-concurrency environments. - Switch to a per-request DbContext pattern. In MVC, the simplest way is to create and dispose the context within your controller:
public class YourController : Controller { private readonly MyDbContext _dbContext; public YourController() { _dbContext = new MyDbContext(); } // Ensure the context is disposed when the controller is garbage-collected protected override void Dispose(bool disposing) { if (disposing) { _dbContext.Dispose(); } base.Dispose(disposing); } } - For a more scalable approach, use a dependency injection container (like Autofac, Unity, or Ninject) to configure the DbContext with a per-request lifetime. This ensures each request gets its own isolated context, eliminating cross-request state corruption.
2. Implement Proper Optimistic Concurrency Control
Even with correct DbContext lifetime, high concurrency can still trigger these exceptions. Add a RowVersion (timestamp) field to your entities to let EF handle concurrency checks automatically:
- Add the field to your entity class:
using System.ComponentModel.DataAnnotations; public class YourEntity { // Your existing properties... [Timestamp] public byte[] RowVersion { get; set; } } - Configure it in your DbContext’s
OnModelCreatingmethod to mark it as a row version:protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<YourEntity>() .Property(e => e.RowVersion) .IsRowVersion(); } - Now, when EF tries to update/delete a record, it will compare the
RowVersionfrom the loaded entity with the one in the database. If they don’t match (meaning the record was modified elsewhere), it throws the exception—you can then catch this and prompt the user to refresh their data and retry, instead of letting the error spread.
3. Audit Application-Level Caching
If you’re caching entities in your business logic or presentation layer, ensure your cache invalidation strategy is solid:
- Clear cached entities immediately after they’re modified—don’t let stale cached data be used to update the database.
- Avoid long-lived caches for frequently modified data. Consider using cache dependencies (e.g., SQL Server cache dependencies) to automatically invalidate cache when database records change.
4. Temporary Mitigation: Sticky Sessions (Not a Long-Term Fix)
If you need a quick stopgap while implementing the above fixes, enable session affinity (sticky sessions) on your load balancer. This routes all requests from a single user to the same IIS server, reducing cross-server context/cache conflicts. Note: This doesn’t fix the underlying issue—it just masks it, so prioritize the permanent fixes above.
5. Add Detailed Logging for Future Debugging
When exceptions occur, log more context to speed up future troubleshooting:
- Capture the entity ID, the user making the request, the server instance ID, and the timestamp of the error.
- If possible, log the current state of the entity vs. the actual state in the database (you can query the database fresh when catching the exception to compare).
Final Notes
Start with fixing the DbContext lifetime—this is almost certainly the root cause of your widespread, restart-resolved exceptions. Then add the RowVersion concurrency control to handle legitimate high-concurrency scenarios, and audit your caching strategy to prevent stale data issues.
内容的提问来源于stack exchange,提问作者thelem

