EntityFramework Core 3.x上下文构造函数可以注入实例呢?
前言
今天讨论的话题来自一位微信好友遇到问题后请求我的帮助,当然他的意图并不是本文标题,只是我将其根本原因进行了一个概括,接下来我们一起来探索标题的问号最终的答案是怎样的呢?
上下文构造函数是否可以注入实例?
老规矩,首先我们定义如下上下文
public class EFCoreDbContext : DbContext
{
public EFCoreDbContext(DbContextOptions<EFCoreDbContext> options) : base(options)
{ }
}
接下来在Web应用程序中如下注入该上下文实例,然后我们就可以开心的玩耍了
services.AddDbContext<EFCoreDbContext>(options =>
{
options.UseSqlServer(@"Server=.;Database=EFCoreTest;Trusted_Connection=True;");
});
问题来了,这位童鞋说,我想要在上述上下文中注入一个实例,当时听到这种情况还比较惊讶,什么情况下才会在上下文构造函数中注入实例呢?我们先不关心这个问题,那还不好说,和正常在ASP.NET Core中使用不就完事了么,实践是检验真理的唯一标准,我们来试试,定义如下接口:
public interface IHello
{
string Say();
} public class Hello : IHello
{
public string Say()
{
return "Hello World";
}
}
接下来则是注入该接口,如下:
services.AddScoped<IHello, Hello>();
然后就来到上下文构造函数中使用该接口,我们搞个方法来测试下看看,如下:
public class EFCoreDbContext : DbContext
{
private readonly IHello _hello;
public EFCoreDbContext(DbContextOptions<EFCoreDbContext> options,
IHello hello) : base(options)
{
_hello = hello;
} public string Print()
{
return _hello.Say();
}
}
最后我们在控制器中使用上下文并调用上述方法,看看是否可行
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private readonly EFCoreDbContext _context; public WeatherForecastController(EFCoreDbContext context)
{
_context = context;
} [HttpGet]
public string Get()
{
return _context.Print();
}
}

呀,没毛病啊,自我感觉甚是良好,莫慌,这位童鞋说这样操作没问题啊,但是我想将上下文注入为实例池的方式,结果却不行,会抛出异常,到底啥异常啊,如下我们修改成实例池的方式瞧瞧:
services.AddDbContextPool<EFCoreDbContext>(options =>
{
options.UseSqlServer(@"Server=.;Database=EFCoreTest;Trusted_Connection=True;");
});

大意为因为该上下文没有只有单个参数是DbContextOptions的构造函数,所以该上下文不能被池化,说明构造函数只能有一个包含DbContextOptions的参数,否则报错,我们还是看看源码中到底是如何实例化实例池的呢?
public DbContextPool([NotNull] DbContextOptions options)
{
_maxSize = options.FindExtension<CoreOptionsExtension>()?.MaxPoolSize ?? DefaultPoolSize; options.Freeze(); _activator = CreateActivator(options); if (_activator == null)
{ //这里抛出上述异常信息
throw new InvalidOperationException(
CoreStrings.PoolingContextCtorError(typeof(TContext).ShortDisplayName()));
}
}
private static Func<TContext> CreateActivator(DbContextOptions options)
{
var constructors
= typeof(TContext).GetTypeInfo().DeclaredConstructors
.Where(c => !c.IsStatic && c.IsPublic)
.ToArray(); if (constructors.Length == )
{
var parameters = constructors[].GetParameters(); if (parameters.Length ==
&& (parameters[].ParameterType == typeof(DbContextOptions)
|| parameters[].ParameterType == typeof(DbContextOptions<TContext>)))
{
return
Expression.Lambda<Func<TContext>>(
Expression.New(constructors[], Expression.Constant(options)))
.Compile();
}
} return null;
}
上述对于实例池是通过表达式来构建的实例池,但是在此之前会做一步验证构造函数参数只能有一个且为DbContextOptions,否则将抛出异常,为何要如此设计呢?我们再来看看在调用上下文实例池到底做了什么呢?如下我只列举出关键信息:
public static IServiceCollection AddDbContextPool<TContextService, TContextImplementation>(
[NotNull] this IServiceCollection serviceCollection,
[NotNull] Action<IServiceProvider, DbContextOptionsBuilder> optionsAction,
int poolSize = )
where TContextImplementation : DbContext, TContextService
where TContextService : class
{ AddCoreServices<TContextImplementation>(
serviceCollection,
(sp, ob) =>
{
......
},
ServiceLifetime.Singleton); ...... }
原来在调用实例池时,添加的所以内部服务都是单例,所以我们可以大胆得出结论:在注入上下文实例池时,添加的内部核心服务是单例,而我们注入的实例可能为其他类型,所以EntityFramework Core做了限定,构造函数只能包含DbContextOptions。那么我们在上下文中怎样才能使用我们注入的实例呢?其实EntityFramework Core考虑到有这样的需求,所以给出了对应解决方案,在上下文中存在GetService方法,是不是很熟悉,不过需要我们导入命名空间【Microsoft.EntityFrameworkCore.Infrastructure】,直接在对应方法中获取注入的实例,这样就绕过了上下文构造函数,如下:
public string Print()
{
return this.GetService<IHello>().Say();
}

哎呀,本以为找到了良药,结果又报错了,这是为何呢?要是我们将注入的实例修改为单例结果将是好使的,我已经亲自验证过,这里就不再浪费篇幅,根本原因在哪里呢?此时我们再来看看上述GetService的实现是怎样的呢?
public static TService GetService<TService>([CanBeNull] IInfrastructure<IServiceProvider> accessor)
{
object service = null; if (accessor != null)
{
var internalServiceProvider = accessor.Instance; service = internalServiceProvider.GetService(typeof(TService))
?? internalServiceProvider.GetService<IDbContextOptions>()
?.Extensions.OfType<CoreOptionsExtension>().FirstOrDefault()
?.ApplicationServiceProvider
?.GetService(typeof(TService)); if (service == null)
{
throw new InvalidOperationException(
CoreStrings.NoProviderConfiguredFailedToResolveService(typeof(TService).DisplayName()));
}
} return (TService)service;
}
是否有种恍然大悟的感觉,这里做了判断,因为在注入上下文实例池时,也注入了核心服务且为单例,但是我们在startup中注入的实例有可能不是单例,比如为scope时,此时会将我们注入的实例通过GetService获取时作为内部服务,所以会出现无法解析的情况并抛出异常,所以为了解决这个问题,我们必须明确告诉EF Core对于哪些ServiceProvider使用内部服务,除此之外,将通过上述ApplicationServiceProvider来获取而不包括内部服务,将内部服务和外部服务做一个明确的区分即可,在EntityFramework Core中对于内部服务的注册,已经通过扩展方法进行了封装,我们只需手动调用即可,最终解决方案如下:
//手动注册针对SQL Server的内部服务
services.AddEntityFrameworkSqlServer(); //内部服务使用对应ServiceProvider
services.AddDbContextPool<EFCoreDbContext>((serviceProvider, options) =>
{
options.UseInternalServiceProvider(serviceProvider);
options.UseSqlServer(@"Server=.;Database=EFCoreTest;Trusted_Connection=True;");
}); services.AddScoped<IHello, Hello>();
总结
本文是以3.x版本演示,对于2.x版本也同样适用,所以不要认为直接通过GetService没抛出异常而认为一切正常,瞎猫碰上死耗子,正是恰好碰到注入的实例为单例而绕过了异常的出现,所以上下构造函数可以注入实例吗,答案是不一定,若为实例池肯定不行,希望通过本文的详细描述能给需要在上下文构造函数中注入实例的童鞋一点力所能及的帮助,探究其问题的本质才能有所成长,感谢您的阅读。
EntityFramework Core 3.x上下文构造函数可以注入实例呢?的更多相关文章
- EntityFramework Core 1.1是如何创建DbContext实例的呢?
前言 上一篇我们简单讲述了在EF Core1.1中如何进行迁移,本文我们来讲讲EF Core1.1中那些不为人知的事,细抠细节,从我做起. 显式创建DbContext实例 通过带OnConfiguri ...
- EntityFramework Core上下文实例池原理分析
前言 无论是在我个人博客还是著作中,对于上下文实例池都只是通过大量文字描述来讲解其基本原理,而且也是浅尝辄止,导致我们对其认识仍是一知半解,本文我们摆源码,从源头开始分析.希望通过本文从源码的分析,我 ...
- EntityFramework Core如何映射动态模型?
前言 本文我们来探讨下映射动态模型的几种方式,相信一部分童鞋项目有这样的需求,比如每天/每小时等生成一张表,此种动态模型映射非常常见,经我摸索,这里给出每一步详细思路,希望能帮助到没有任何头绪的童鞋, ...
- EntityFramework Core依赖注入上下文方式不同造成内存泄漏了解一下?
前言 这个问题从未遇见过,是一位前辈问我EF Core内存泄漏问题时我才去深入探讨这个问题,刚开始我比较惊讶,居然还有这种问题,然后就有了本文,直接拿前辈的示例代码并稍加修改成就了此文,希望对在自学E ...
- Webservice WCF WebApi 前端数据可视化 前端数据可视化 C# asp.net PhoneGap html5 C# Where 网站分布式开发简介 EntityFramework Core依赖注入上下文方式不同造成内存泄漏了解一下? SQL Server之深入理解STUFF 你必须知道的EntityFramework 6.x和EntityFramework Cor
Webservice WCF WebApi 注明:改编加组合 在.net平台下,有大量的技术让你创建一个HTTP服务,像Web Service,WCF,现在又出了Web API.在.net平台下, ...
- Cookies 初识 Dotnetspider EF 6.x、EF Core实现dynamic动态查询和EF Core注入多个上下文实例池你知道有什么问题? EntityFramework Core 运行dotnet ef命令迁移背后本质是什么?(EF Core迁移原理)
Cookies 1.创建HttpCookies Cookie=new HttpCookies("CookieName");2.添加内容Cookie.Values.Add(&qu ...
- EntityFramework Core 2.0执行原始查询如何防止SQL注入?
前言 接下来一段时间我们来讲讲EntityFramework Core基础,精简的内容,深入浅出,希望为想学习EntityFramework Core的童鞋提供一点帮助. EntityFramewor ...
- 浅谈.net core如何使用EFCore为一个上下文注类型注入多个实例用于连接主从数据库
在很多一主多从数据库的场景下,很多开发同学为了复用DbContext往往采用创建一个包含所有DbSet<Model>父类通过继承派生出Write和ReadOnly类型来实现,其实可以通过命 ...
- Asp.Net Core 项目实战之权限管理系统(3) 通过EntityFramework Core使用PostgreSQL
0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...
随机推荐
- Simulink仿真入门到精通(十九) 总结回顾&自我练习
从2019年12月27到2020年2月12日,学习了Simulink仿真及代码生成技术入门到精通,历时17天. 学习的比较粗糙,有一些地方还没理解透彻,全书梳理总结: Simulink的基础模块已基本 ...
- C语言程序设计(五) 选择控制结构
第五章 选择控制结构 分治策略:任务分解细化 程序设计语言:为了让计算机执行由高级语言编写的程序指令,必须把这些指令从高级语言形式转换成计算机能理解的机器语言形式,这种转换是由编译器来完成的 算法:为 ...
- postgresql自增字段初始值的设定
在实际开发中会有这样的需求,想要自己设置表中自增字段的初始值. 比如:有一个your_table表中有一个自增字段id,我们知道,插入数据后,默认是从1开始自增的. 但是假如现在有一个需求,是要求id ...
- StormDRPC流程解读
Storm 的编程模型是一个有向无环图,模型角度决定了 Storm 的 Spout 接收到外部系统的请求,将请求数据分发给下游的 bolt 进行处理后,spout 并不能得到 bolt 的处理结果并将 ...
- LSTM + linear-CRF序列标注笔记
CRF 许多随机变量组成一个无向图G = {V, E},V代表顶点,E代表顶点间相连的边, 每个顶点代表一个随机变量,边代表两个随机变量间存在相互影响关系(变量非独立), 如果随机变量根据图的结构而具 ...
- 广告行业中那些趣事系列7:实战腾讯开源的文本分类项目NeuralClassifier
摘要:本篇主要分享腾讯开源的文本分类项目NeuralClassifier.虽然实际项目中使用BERT进行文本分类,但是在不同的场景下我们可能还需要使用其他的文本分类算法,比如TextCNN.RCNN等 ...
- python基于scrapy框架的反爬虫机制破解之User-Agent伪装
user agent是指用户代理,简称 UA. 作用:使服务器能够识别客户使用的操作系统及版本.CPU 类型.浏览器及版本.浏览器渲染引擎.浏览器语言.浏览器插件等. 网站常常通过判断 UA 来给不同 ...
- 分享CCNTFS小工具,在 macOS 中完全读写、修改、访问Windows NTFS硬盘的文件,无须额外的驱动(原生驱动)更稳定,简单设置即可高速传输外接NTFS硬盘文件
CCNTFS [ 下载 ] 在 macOS 中完全读写.修改.访问Windows NTFS硬盘的文件,无须额外的驱动(原生驱动)更稳定,安装后进行简单设置即可高速传输外接NTFS硬盘文件,可全程离线使 ...
- Servlet(三)----Servlet体系与HTTP
## Servlet的体系结构 Servlet --- 接口 | | GenericServlet --- 抽象类 | | HttpServlet -- 抽象类 GenericServle ...
- CF1324C Frog Jumps 题解
原题链接 简要题意: 现在河面上有 \(n+2\) 块石头,编号 \(0\) 到 \(n+1\),\(1\)~\(n\) 块石头每块上有一个方向,如果是 \(L\),那么青蛙到这块石头上之后只能往左跳 ...