《HiBlogs》重写笔记[1]--从DbContext到依赖注入再到自动注入
本篇文章主要分析DbContext的线程内唯一,然后ASP.NET Core的注入,再到实现自动注入。
DbContext为什么要线程内唯一(非线程安全)
我们在使用EF的时候,可能使用相关框架封装过了,也可能是自己直接使用DbContext。但是有没有想过怎么使用DbContext才是正确的姿势呢?
DbContext可以访问操作所有数据表、保持跟踪状态、SaveChanges统一提交等等强大的功能。我们会不会想,它的创建和销毁是否要付出昂贵的代价?
其实不是的,DataContext 是轻量的,创建它不需要很大的开销。
在EF6的DbContext文档 https://msdn.microsoft.com/zh-cn/library/system.data.entity.dbcontext(v=vs.113).aspx 最下面有句话 此类型的任何公共 static(在 Visual Basic 中为 Shared) 成员都是线程安全的。但不保证所有实例成员都是线程安全的。
DbContext实例不保证线程安全。也就是说多线程同时操作一个DbContext实例,可能会有意想不到的问题。
比如我前面的文章 http://www.cnblogs.com/zhaopei/p/async_two.html 遇到的问题就是如此
之所以本地iis和iis express测试都是没问题,是因为本地访问速度快,没有并发。
更加极端点的体现,全局使用一个静态DbContext实例(之前我就这么想过)。
比如:线程a正在修改一个实体到一半,线程b给不小心保存了。线程c在修改一个实体,线程d又把这个实体不小心删了。这玩笑就开大了。并发越大,此类情况越多。所以DbContext实例只能被单个线程访问。还有,在执行异步的方法的时候切不可能自认为的“效率提升”同时发起多个异步查询。
当然,这也只是我个人认为可能存在的问题。不过你只要记住DbContext不是线程安全类型就够了。
如此,我们是不是应该每次数据操作都应该实例一个新的DbContext呢?也不尽然。比如方法a中的DbContext实例查询出实体修改跟踪,并把实体传入了方法b,而方法b是另外实例的DbContext,那么在方法b中就不能保存方法a传过来的实体了。如果非得这么做方法b中的DbContext也应该由方法a传过来。也就是说我们要的效果是线程内的DbContext实例唯一。
DbContext怎么做到线程内唯一(依赖注入)
在使用EF x时你可能是
public static BlogDbContext dbEntities
{
get
{
DbContext dbContext = CallContext.GetData("dbContext") as DbContext;
if (dbContext == null)
{
dbContext = new BlogDbContext();
//将新创建的 ef上下文对象 存入线程
CallContext.SetData("dbContext", dbContext);
}
return dbContext as BlogDbContext;
}
}
而在EF Core中没有了CallContext。其实我们不需要CallContext,通过自带的注入框架就可以实现线程内唯一。
我们来写个demo
首先创建一个类库,通过注入得到DbContext。然后在web里面也注入一个DbContext,然后在web里面调用类库里面的方法。验证两个DbContext的GetHashCode()值是否一致。
类库内获取DbContext的HashCode
namespace DemoLibrary
{
public class TempDemo
{
BloggingContext bloggingContext;
public TempDemo(BloggingContext bloggingContext)
{
this.bloggingContext = bloggingContext;
}
//获取DbContext的HashCode
public int GetDBHashCode()
{
return bloggingContext.GetHashCode();
}
}
}
然后在web里面也注入DbContext,并对比HashCode
public IActionResult Index()
{
// 获取类库中的DbContext实例Code
var code1 = tempDemo.GetDBHashCode();
// 获取web启动项中DbContext实例Code
var code2 = bloggingContext.GetHashCode();
return View();
}
效果图:
由此可见通过注入得到的DbContext对象是同一个(起码在一个线程内是同一个)
另外,我们还可以反面验证通过new关键字实例DbContext对象在线程内不是同一个
为什么可以通过注入的方式得到线程内唯一(注入的原理)
这里不说注入的定义,也不说注入的好处有兴趣可查看。我们直接来模拟实现注入功能。
首先我们定义一个接口IUser和一个实现类User
public interface IUser
{
string GetName();
}
public class User : IUser
{
public string GetName()
{
return "农码一生";
}
}
然后通过不同方式获取User实例
第一种不用说大家都懂的
第二种和第三种我们看到使用到了DI类(自己实现的一个简易注入"框架"),下面我们来看看DI类中的Resolve到底是个什么鬼
public class DI
{
//通过反射 获取实例 并向上转成接口类型
public static IUser Resolve(string name)
{
Assembly assembly = Assembly.GetExecutingAssembly();//获取当前代码的程序集
return (IUser)assembly.CreateInstance(name);//这里写死了,创建实例后强转IUser
}
//通过反射 获取“一个”实现了此接口的实例
public static T Resolve<T>()
{
Assembly assembly = Assembly.GetExecutingAssembly();
//获取“第一个”实现了此接口的实例
var type = assembly.GetTypes().Where(t => t.GetInterfaces().Contains(typeof(T))).FirstOrDefault();
if (type == null)
throw new Exception("没有此接口的实现");
return (T)assembly.CreateInstance(type.ToString());//创建实例 转成接口类型
}
是不是想说“靠,这么简单”。简单的注入就这样简单的实现了。如果是相对复杂点的呢?比如我们经常会用到,构造注入里面的参数本身也需要注入。
比如我们再创建一个IUserService接口和一个UserService类
public interface IUserService
{
IUser GetUser();
}
public class UserService : IUserService
{
private IUser _user;
public UserService(IUser user)
{
_user = user;
}
public IUser GetUser()
{
return _user;
}
}
我们发现UserService的构造需要传入IUser,而IUser的实例使用也是需要注入IUser的实例。
这里需要思考的就是userService.GetUser()怎么可以得到IUser的实现类实例。所以,我们需要继续看Resolve2的具体实现了。
public static T Resolve2<T>()
{
Assembly assembly = Assembly.GetExecutingAssembly();//获取当前代码的程序集
//获取“第一个”实现了此接口的实例(UserService)
var type = assembly.GetTypes().Where(t => t.GetInterfaces().Contains(typeof(T))).FirstOrDefault();
if (type == null)
throw new Exception("没有此接口的实现");
var parameter = new List<object>();
//type.GetConstructors()[0]获取第一个构造函数 GetParameters的所有参数(IUser接口)
var constructorParameters = type.GetConstructors()[0].GetParameters();
foreach (var constructorParameter in constructorParameters)
{
//获取实现了(IUser)这个接口类型(User)
var tempType = assembly.GetTypes().Where(t => t.GetInterfaces()
.Contains(Type.GetType(constructorParameter.ParameterType.FullName)))
.FirstOrDefault();
//并实例化成对象(也就是User实例) 添加到一个集合里面 供最上面(UserService)的注入提供参数
parameter.Add(assembly.CreateInstance(tempType.ToString()));
}
//创建实例,并传入需要的参数 【public UserService(IUser user)】
return (T)assembly.CreateInstance(type.ToString(), true, BindingFlags.Default, null, parameter.ToArray(), null, null);//true:不区分大小写
}
仔细看了也不难,就是稍微有点绕。
既然知道了注入的原理,那我们控制通过方法A注入创建实例每次都是重新创建、通过方法B创建的实例在后续参数使用相同的实例、通过方便C创建的实例全局单例,就不是难事了。
以下伪代码:
//每次访问都是新的实例(通过obj1_1、obj1_2可以体现)
public static T Transient<T>()
{
//var obj1_1 = assembly.CreateInstance(name);
//var obj2 = assembly.CreateInstance(obj1_1,...)
//var obj1_2 = assembly.CreateInstance(name);
//var obj3 = assembly.CreateInstance(obj1_2,...)
//var obj4 = assembly.CreateInstance(,...[obj2,obj3],...)
//return (T)obj4;
}
//一次请求中唯一实例(通过obj1可以体现)
public static T Scoped<T>()
{
//var obj1 = assembly.CreateInstance(name);
//var obj2 = assembly.CreateInstance(obj1,...)
//var obj3 = assembly.CreateInstance(obj1,...)
//var obj4 = assembly.CreateInstance(,...[obj2,obj3],...)
//return (T)obj4;
}
//全局单例(通过obj1 == null可以体现)
public static T Singleton<T>()
{
//if(obj1 == null)
// obj1 = assembly.CreateInstance(name);
//if(obj2 == null)
// obj2 = assembly.CreateInstance(obj1,...)
//if(obj3 == null)
// obj3 = assembly.CreateInstance(obj1,...)
//if(obj4 == null)
// obj4 = assembly.CreateInstance(,...[obj2,obj3],...)
//return (T)obj4;
}
通过伪代码,应该不难理解怎么通过注入框架实现一个请求内实现DbContext的唯一实例了吧。
同时也应该更加深刻的理解了ASP.NET Core中对应的AddScoped、AddTransient、AddSingleton这三个方法和生命周期了吧。
在ASP.NET Core中实现自动注入
不知道你有没有在使用AddScoped、AddTransient、AddSingleton这类方法的时候很烦。每次要使用一个对象都需要手动注入,每次都要到Startup.cs文件里面去做对应的修改。真是烦不胜烦。
使用过ABP的同学就有种感觉,那就是根本体会不到注入框架的存在。我们写的接口和实现都自动注入了。使用的时候直接往构造函数里面扔就好了。那我们在使用ASP.NET Core的时候很是不是也可以实现类似的功能呢?
答案是肯定的。我们先定义这三种生命周期的标识接口,这三个接口仅仅只是做标记作用。(名字你可以随意)
// 瞬时(每次都重新实例)
public interface ITransientDependency
//一个请求内唯一(线程内唯一)
public interface IScopedDependency
//单例(全局唯一)
public interface ISingletonDependency
我们以ISingletonDependency为例
/// 自动注入
/// </summary>
private void AutoInjection(IServiceCollection services, Assembly assembly)
{
//获取标记了ISingletonDependency接口的接口
var singletonInterfaceDependency = assembly.GetTypes()
.Where(t => t.GetInterfaces().Contains(typeof(ISingletonDependency)))
.SelectMany(t => t.GetInterfaces().Where(f => !f.FullName.Contains(".ISingletonDependency")))
.ToList();
//获取标记了ISingletonDependency接口的类
var singletonTypeDependency = assembly.GetTypes()
.Where(t => t.GetInterfaces().Contains(typeof(ISingletonDependency)))
.ToList();
//自动注入标记了 ISingletonDependency接口的 接口
foreach (var interfaceName in singletonInterfaceDependency)
{
var type = assembly.GetTypes().Where(t => t.GetInterfaces().Contains(interfaceName)).FirstOrDefault();
if (type != null)
services.AddSingleton(interfaceName, type);
}
//自动注入标记了 ISingletonDependency接口的 类
foreach (var type in singletonTypeDependency)
{
services.AddSingleton(type, type);
}
然后在Startup.cs文件的ConfigureServices方法里调用下就好了
public void ConfigureServices(IServiceCollection services)
{
var assemblyWeb = Assembly.GetExecutingAssembly();
// 自动注入
AutoInjection(services, assemblyApplication);
这样以后我们只要给某个接口和类定义了ISingletonDependency接口就会被自动单例注入了。是不是很酸爽!
什么?反射低效?别闹了,这只是在程序第一次启动的时候才运行的。
嗨-博客,的源代码就是如此实现。
当然,给你一个跑不起来的Demo是很痛苦的,没有对应源码的博文看起来更加痛苦。特别是总有这里或那里有些细节没注意,导致达不到和博文一样的效果。
所以我又另外重写了一个Demo。话说,我都这么体贴了你不留下赞了再走真的好吗?如果能在github上送我颗星星就再好不过了!
-------------------- 更新 -------------------------
基于园友对关于DbContext能不能单次请求内唯一?DbContex需不需要主动释放?:http://www.cnblogs.com/zhaopei/p/dispose-on-dbcontext.html
博文源码
嗨博客,基于ASP.NET COre 2.0的跨平台的免费开源博客
https://github.com/zhaopeiym/Hi-Blogs (求⭐⭐)demo
https://github.com/zhaopeiym/BlogDemoCode/tree/master/依赖注入/DIDemo
相关资料
- http://www.cnblogs.com/hjf1223/archive/2010/10/10/static_datacontext.html
- http://www.cnblogs.com/xishuai/p/ef-dbcontext-thread-safe.html
《HiBlogs》重写笔记[1]--从DbContext到依赖注入再到自动注入的更多相关文章
- 笔记:NPM 无限需要依赖问题解决
笔记:NPM 无限需要依赖问题解决 起因 因为想学一下 VUE,开始跟着教程一步一步输出命令,开始也没有什么问题,一切都很顺利. 突然不知道是哪一步出了问题,一直让我安装依赖,没完没了,开始并不觉得有 ...
- (转)Spring读书笔记-----Spring核心机制:依赖注入
Java应用(从applets的小范围到全套n层服务端企业应用)是一种典型的依赖型应用,它就是由一些互相适当地协作的对象构成的.因此,我们说这些对象间存在依赖关系.加入A组件调用了B组件的方法,我们就 ...
- Spring读书笔记-----Spring核心机制:依赖注入
spring框架为我们提供了三种注入方式,分别是set注入,构造方法注入,接口注入.今天就和大家一起来学习一下 依赖注入的基本概念 依赖注入(Dependecy Injection),也称为IoC(I ...
- 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(6)-Unity 2.x依赖注入by运行时注入[附源码]
原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(6)-Unity 2.x依赖注入by运行时注入[附源码] Unity 2.x依赖注入(控制反转)IOC,对 ...
- spring 四种依赖注入方式以及注解注入方式
平常的java开发中,程序员在某个类中需要依赖其它类的方法,则通常是new一个依赖类再调用类实例的方法,这种开发存在的问题是new的类实例不好统一管理,spring提出了依赖注入的思想,即依赖类不由程 ...
- 借助微软提供的url重写类库URLRewriter.dll(1.0)实现程序自动二级域名,域名需要泛解析
二级域名和系统中会员帐号自动关联,也就是系统中注册一个会员,会员自动就可以通过二级域名来访问,比如我的帐号是zhangsan,我在morecoder.com注册后,访问zhangsan.morecod ...
- Spring Boot笔记二:快速创建以及yml文件自动注入
上个笔记写了如何自己去创建Spring boot,以及如何去打jar包,其实,还是有些麻烦的,我们还自己新建了几个文件夹不是. Idea可以让我们快速的去创建Spring boot应用,来看 一.快速 ...
- Spring.NET依赖注入框架学习--简单对象注入
Spring.NET依赖注入框架学习--简单对象注入 在前面的俩篇中讲解了依赖注入的概念以及Spring.NET框架的核心模块介绍,今天就要看看怎么来使用Spring.NET实现一个简单的对象注入 常 ...
- sql注入学习笔记,什么是sql注入,如何预防sql注入,如何寻找sql注入漏洞,如何注入sql攻击 (原)
(整篇文章废话很多,但其实是为了新手能更好的了解这个sql注入是什么,需要学习的是文章最后关于如何预防sql注入) (整篇文章废话很多,但其实是为了新手能更好的了解这个sql注入是什么,需要学习的是文 ...
随机推荐
- JS组件系列——再推荐一款好用的bootstrap-select组件,亲测还不错
前言:之前分享过两篇bootstrap下拉框的组件:JS组件系列——两种bootstrap multiselect组件大比拼 和 JS组件系列——Bootstrap Select2组件使用小结 ,收 ...
- mysql语句的一个问题
刚才在群里有个同学提出了这么一个问题 在Mybatis的mapper文件中有一条语句这么写 说是系统不报错,也没返回,我一看句子应该没什么问题.执行的时候应该是PreparedStatement 执行 ...
- BeautifulSoup练习第一节
一.pip install beautilfulsoup4 二.主要使用html.parser这个python标准库 三.打印首页博客的时间.打印摘要 # coding:utf-8from bs4 i ...
- SpringMVC知识点小结
SpringMVC: 1.SpringMVC和Spring的关系: 软件开发的三层架构: web层[表示层.表现层]---->Service层---->Dao[DataBase Acces ...
- JS判断当前使用设备是pc端还是web端(转MirageFireFox)
js判断当前设备 最近用bootstrap做自适应,发现仍然很难很好的兼容web端和PC端的现实. 仔细观察百度,淘宝,京东等大型网站,发现这些网站都有对应不同客户端的子站. 站点 PC端url we ...
- 深入剖析ConcurrentHashMap 一
详见:http://blog.yemou.net/article/query/info/tytfjhfascvhzxcyt201 ConcurrentHashMap是Java5中新增加的一个线程安全的 ...
- 入侵检测工具之RKHunter & AIDE
一.AIDE AIDE全称为(Adevanced Intrusion Detection Environment)是一个入侵检测工具,主要用于检查文件的完整性,审计系统中的工具是否被更改过. AIDE ...
- JS基础--函数与BOM、DOM操作、JS中的事件以及内置对象
前 言 絮叨絮叨 这里是JS基础知识集中讲解的第三篇,也是最后一篇,三篇JS的基础,大多是知识的罗列,并没有涉及更难得东西,干货满满!看完这一篇后,相信许多正在像我一样正处于初级阶段的同学, ...
- Swing-JPopupMenu弹出菜单用法-入门
弹出菜单是GUI程序中非常常见的一种控件.它通常由鼠标右击事件触发,比如在windows系统桌面上右击时,会弹出一个包含“刷新”.“属性”等菜单的弹出菜单.Swing中的弹出菜单是JPopupMenu ...
- 团队作业4——第一次项目冲刺(Alpha版本)4.25
团队作业4--第一次项目冲刺(Alpha版本) Day four: 会议照片 每日站立会议: 项目进展 今天是项目的Alpha敏捷冲刺的第四天,先大概整理下昨天已完成的任务以及今天计划完成的任务.今天 ...