最近把后台管理程序换成nop方式。
在使用_productService.Update(M); 时碰到问题,更新不成功。
刚开始还以为是EF的问题,因为是先_productService.GetProductById(id), 再update,难道get的实体类不是要更新的数据吗?
然后查了下EF更新的三种情况:
1.最基本的,先查出来再更新,EF会跟踪查询的结果,看是否有新的更新,如果有只更新修改的字段,如果没有修改内容则不更新数据库。
TestDbContext db = new TestDbContext(); var test = db.Tests.Find(1); test.Remarks = "更新字段方法1"; db.SaveChanges();
2.比第一种情况少了查询步骤。先New一个实体类并且赋值,再通过dbcontext的db.Entry(test).State设置该实体类对应的状态是modified,然后savechange()即可,此方法会更新表的每个字段,没有赋值就是Null。
TestDbContext db = new TestDbContext(); Test test = new Test() { ID = 1, Remarks = "更新字段方法2" }; db.Entry(test).State = System.Data.Entity.EntityState.Modified; db.SaveChanges();
3.也不用先查询该数据,而且不会更新全部字段,只更新设置了IsModified = true的字段。New一个实体类并赋值,db.Tests.Attach(test);把该实体类添加到dbcontext,然后db.Entry(test).Property("Remarks").IsModified = true;对需要修改的字段设置属性,只会对设置属性的字段更新。
TestDbContext db = new TestDbContext(); Test test = new Test() { ID = 1, Remarks = "更新字段方法3" }; db.Tests.Attach(test); db.Entry(test).Property("Remarks").IsModified = true; db.SaveChanges();
使用 SQL Server Profiler 跟踪数据库发现_productService.GetProductById(id)这个步骤没有产生sql语句,进去该方法,猜测应该是缓存的问题,确定不是EF的问题。
_productService.GetProductById方法:
public M_Device GetProductById(int productId) { if (productId == 0) return null; string key = string.Format(BSRPRODUCTS_BY_ID_KEY, productId); return _cacheManager.Get(key, () => _productRepository.GetById(productId)); }
其中: _cacheManager.Get(key, () => _productRepository.GetById(productId)) 是先查 _cacheManager.Get<M_Device>(key)看是否有值,否则执行后面的lambda表达式。
调试发现_cacheManager.Get<M_Device>(key)一直有值,难怪更新不成功,是因为没有执行GetById方法。
查nop代码:
nop有三种缓存:HttpContextBase.Items键值对、Redis、MemoryCache。
在依赖注入程序里有:
if (config.RedisCachingEnabled) { builder.RegisterType<RedisConnectionWrapper>().As<IRedisConnectionWrapper>().SingleInstance(); builder.RegisterType<RedisCacheManager>().As<ICacheManager>().Named<ICacheManager>("nop_cache_static").InstancePerLifetimeScope(); } else { builder.RegisterType<MemoryCacheManager>().As<ICacheManager>().Named<ICacheManager>("nop_cache_static").SingleInstance(); } builder.RegisterType<PerRequestCacheManager>().As<ICacheManager>().Named<ICacheManager>("nop_cache_per_request").InstancePerLifetimeScope();
Redis 和 MemoryCache 会二选一,我的程序中暂时使用的 MemoryCache 。
在CacheExtensions.cs里看到默认有效期是60分钟。
调试发现return _cacheManager.Get(key, () => _productRepository.GetById(productId)); 里的cache竟然是 MemoryCache(估计是因为在注入程序中把PerRequestCache的实例化注销了导致)。
builder.RegisterType<bsrProductService>().As<IbsrProductService>().InstancePerLifetimeScope();
一般service里的cache默认是PerRequestCache。如果要注入 MemoryCache需要传入参数:
builder.RegisterType<bsrProductController>() .WithParameter(ResolvedParameter.ForNamed<ICacheManager>("nop_cache_static"));
查了下PerRequestCache的 HttpContextBase.Items键值对,
HttpContext基于HttpApplication的处理管道,由于HttpContext对象贯穿整个处理过程,所以,可以从HttpApplication处理管道的前端将状态数据传递到管道的后端,完成状态的传递任务。 HttpContext的生命周期从服务器接收的HTTP请求开始到反应发送回客户端结束。 在WebForm或类库(包括MVC)项目中,通过Current静态属性,就能够获得HttpContext的对象。 HttpContext context = HttpContext.Current; 如果是在Asp.net MVC的Controller中,通过this.HttpContext;就能获取到HttpContextBase对象。 HttpContextBase context = this.HttpContext; 如果是在MVC视图中可以这样得到: @Html.ViewContext.HttpContext 在MVC中是HttpContextBase在WebForm中是HttpContext。
HttpContext的Items是IDictionary键/值对的对象集合,在HttpRequest的生存期中共享。它只存在于HttpRequest中。
所以:
点击编辑进入详情页的时候虽然调用了_productService.GetProductById(id), 缓存了该条数据(HttpContextBase.Items缓存,客户端显示正常后缓存就结束了), 编辑数据点击保存后,
先调用 _productService.GetProductById(id) 即 return _cacheManager.Get(key, () => _productRepository.GetById(productId)); 里的cache并没有该值,因为点击保存又是另外的HttpRequest了。再update,发现成功更新。