《HiBlogs》重写笔记[1]--从DbContext到依赖注入再到自动注入
阅读目录
DbContext为什么要线程内唯一(非线程安全)
DbContext怎么做到线程内唯一(依赖注入)
为什么可以通过注入的方式得到线程内唯一(注入的原理)
在ASP.NET Core中实现自动注入
博文源码
相关资料
本篇文章主要分析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()
{
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()
{
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