EF Core继承CachedRepository引发System.InvalidOperationException异常
问题解决:DbContext并发操作异常
问题根源
你的代码里,GetSomeEntity方法在调用GetCacheItem时,提前启动了EF Core查询任务(func(id)直接执行FirstOrDefaultAsync()并返回Task)。当多个请求同时访问同一个未缓存的key时,多个查询Task会共享同一个Scoped生命周期的DbContext,触发并发操作DbContext的异常。
解决方案
1. 修改缓存方法参数为延迟执行的委托
将GetCacheItem的参数从Task<T>改为Func<Task<T>>,确保只有当缓存不存在时才启动DB查询,同时添加缓存锁避免并发查询(缓存击穿):
public class CachedRepository { private readonly IMemoryCache cache; const int expirationTime = 20; public CachedRepository(IMemoryCache cache) { this.cache = cache; } protected async Task<T> GetCacheItem<T>(Func<Task<T>> fetchData, string key, int expirationMinutes = expirationTime) { string cacheKey = $"{key}"; // 第一次检查缓存 if (cache.TryGetValue(cacheKey, out T cacheItem)) { return cacheItem; } // 创建缓存锁,防止并发击穿DB using var lockEntry = cache.CreateEntry($"lock_{cacheKey}"); lockEntry.SetValue(true); lockEntry.AbsoluteExpirationRelativeToNow = TimeSpan.FromSeconds(5); // 设置锁过期时间,避免死锁 // 二次检查缓存:等待锁期间可能已经被其他请求写入缓存 if (cache.TryGetValue(cacheKey, out cacheItem)) { return cacheItem; } // 执行DB查询(此时只有一个请求在执行) cacheItem = await fetchData(); // 写入缓存 cache.Set(cacheKey, cacheItem, DateTimeOffset.UtcNow.AddMinutes(expirationMinutes)); return cacheItem; } }
2. 修改仓储方法的调用方式
传递延迟执行的委托,而不是提前启动的Task:
public class SomeEntityRepository : CachedRepository { private readonly SomeContext context; private readonly string repoName; public SomeEntityRepository (SomeContext context, IMemoryCache cache) : base(cache) { this.context = context; this.repoName = GetType().FullName; } public async Task<SomeEntity> GetSomeEntity(int id) { const string methodName = nameof(GetSomeEntity); string cacheKey = $"{repoName}_{methodName}_{id.ToString()}"; // 传递Func<Task<SomeEntity>>,而非已启动的Task return await GetCacheItem(() => context.SomeEntities .AsNoTracking() .Where(x => x.Id == id) .FirstOrDefaultAsync(), cacheKey); } }
补充说明
- 不要将DbContext改为Transient生命周期:这会导致上下文实例过多,耗尽数据库连接池,反而引发新问题。Scoped生命周期是Web应用中DbContext的正确选择。
- 缓存锁的作用:当缓存失效时,确保只有一个请求去查询DB,其他请求等待缓存写入后直接读取,既避免DbContext并发,也减轻DB压力。
内容的提问来源于stack exchange,提问作者Kent Kostelac
相关产品推荐
相关产品推荐

