启用MARS的ASP.NET Web API缓存强制重置方案问询
首先,咱们先把核心问题拆解清楚:你遇到的是数据库数据修改后前端无法显示最新内容,排查后排除了WCF服务缓存的问题,但发现两个关键点:Entity Framework启用了MARS(Multiple Active Result Sets),同时代码里还有基于用户Session的自定义缓存CardRegistrationPartyCache。这两个都是导致旧数据残留的潜在原因,咱们一个个来解决。
一、核心问题定位
1. 自定义Session缓存是首要嫌疑
看你的CardRegistrationPartyService代码:缓存逻辑是先从Session里取数据,只有缓存不存在时才会调用数据库接口加载并更新缓存。但问题在于——当数据库里的数据被修改后,这个缓存并没有被同步更新!所以前端拿到的始终是Session里的旧数据,这大概率是最直接的原因。
2. MARS会话缓存的影响
启用MARS后,SqlClient会在连接内缓存MARS逻辑会话来降低开销,但这确实可能导致旧的查询结果被复用。不过禁用MARS需要修改多个服务的连接字符串,成本太高,咱们先优先解决自定义缓存的问题,再处理MARS的潜在影响。
二、针对性解决方案
方案1:修改操作同步更新自定义缓存(最直接有效)
既然缓存是存在用户Session里的,那在执行数据修改(更新/删除)的方法里,主动同步更新或移除对应缓存项即可:
示例:更新数据时同步缓存
public void UpdateCardRegistrationParty(CardRegistrationParty updatedCrp) { // 第一步:先执行数据库更新操作 Infrastructure.CreateTestsOfEriService().UpdateCardRegistrationParty(updatedCrp); // 第二步:同步更新Session缓存 var cache = new CardRegistrationPartyCache(Infrastructure.CreateUserSessionService()); cache.AddOrUpdate(updatedCrp); }
示例:删除数据时移除缓存
public void DeleteCardRegistrationParty(int barcodeID) { // 第一步:执行数据库删除 Infrastructure.CreateTestsOfEriService().DeleteCardRegistrationParty(barcodeID); // 第二步:移除对应缓存键 var cache = new CardRegistrationPartyCache(Infrastructure.CreateUserSessionService()); string cacheKey = cache.GetType().FullName + barcodeID.ToString(); Infrastructure.CreateUserSessionService().RemoveUserData(cacheKey); }
方案2:前端层面主动清除缓存(AngularJS端处理)
如果后端修改缓存的成本较高,可以在前端操作完成后,主动清除对应的数据缓存:
示例:AngularJS服务中清除缓存
app.factory('cardRegistrationService', function($http, $cacheFactory) { // 创建专属缓存 var cardCache = $cacheFactory('cardRegistrationCache'); return { // 获取数据时使用缓存 getPartyList: function(barcodeIds) { var url = `/api/cardparties?barcodes=${barcodeIds.join(',')}`; return $http.get(url, { cache: cardCache }); }, // 更新数据后清除缓存 updateParty: function(updatedParty) { return $http.put('/api/cardparties', updatedParty) .then(function(response) { // 清除对应缓存键,下次请求会重新拉取最新数据 cardCache.remove(`/api/cardparties?barcodes=${updatedParty.BarcodeID}`); return response; }); } }; });
方案3:优化EF层面的缓存(解决MARS潜在影响)
如果MARS导致数据库连接层面的旧数据残留,可以通过以下方式优化:
- 禁用EF的跟踪缓存:在查询时添加
AsNoTracking(),避免DbContext缓存旧数据
// 在获取DTO的方法中修改查询逻辑 public IEnumerable<CardRegistrationPartyDTO> GetCardRegistrationPartyListWithChecks(int[] barcodeIDs) { using(var dbContext = new YourDbContext()) { return dbContext.CardRegistrationParties .AsNoTracking() // 禁用EF的一级缓存 .Where(crp => barcodeIDs.Contains(crp.BarcodeID)) .ToList(); } }
- 确保每次操作使用新的DbContext:避免复用同一个DbContext导致旧数据残留,比如在
Infrastructure.CreateTestsOfEriService()中每次都创建新的上下文实例。
方案4:给缓存添加过期策略(长期优化)
给自定义Session缓存加上过期时间,即使忘记同步更新,缓存过期后也会自动从数据库重新加载:
示例:扩展Session缓存的过期逻辑
// 先定义带过期时间的缓存项类 public class CachedItem<T> { public T Value { get; set; } public DateTime ExpiresAt { get; set; } } // 扩展UserSessionService的方法 public void SetUserDataWithExpiry(string key, object value, TimeSpan expiry) { HttpContext.Current.Session[key] = new CachedItem<object> { Value = value, ExpiresAt = DateTime.Now.Add(expiry) }; } public T UserData<T>(string key) { var cachedItem = HttpContext.Current.Session[key] as CachedItem<object>; if(cachedItem == null || cachedItem.ExpiresAt < DateTime.Now) { // 缓存过期或不存在,移除并返回默认值 HttpContext.Current.Session.Remove(key); return default(T); } return (T)cachedItem.Value; }
然后修改CardRegistrationPartyCache的AddOrUpdate方法,设置比如10分钟的过期时间:
public void AddOrUpdate(CardRegistrationParty crp) { string key = this.GetType().FullName + crp.BarcodeID.ToString(); _userSessionService.SetUserDataWithExpiry(key, crp, TimeSpan.FromMinutes(10)); }
三、验证步骤
- 先验证自定义缓存是否是根源:修改数据后,手动清除当前用户的Session(比如重启服务或调用缓存移除方法),看前端是否显示最新数据。
- 如果是,优先实施方案1,确保修改操作同步更新缓存。
- 再测试EF层面的优化,添加
AsNoTracking()后观察数据是否正常更新。 - 最后根据需求考虑前端缓存清理或过期策略的长期优化。
内容的提问来源于stack exchange,提问作者Tibomso

