前言

今天讨论的话题来自一位微信好友遇到问题后请求我的帮助,当然他的意图并不是本文标题,只是我将其根本原因进行了一个概括,接下来我们一起来探索标题的问号最终的答案是怎样的呢?

上下文构造函数是否可以注入实例?

老规矩,首先我们定义如下上下文

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上下文构造函数可以注入实例呢?的更多相关文章

  1. EntityFramework Core 1.1是如何创建DbContext实例的呢?

    前言 上一篇我们简单讲述了在EF Core1.1中如何进行迁移,本文我们来讲讲EF Core1.1中那些不为人知的事,细抠细节,从我做起. 显式创建DbContext实例 通过带OnConfiguri ...

  2. EntityFramework Core上下文实例池原理分析

    前言 无论是在我个人博客还是著作中,对于上下文实例池都只是通过大量文字描述来讲解其基本原理,而且也是浅尝辄止,导致我们对其认识仍是一知半解,本文我们摆源码,从源头开始分析.希望通过本文从源码的分析,我 ...

  3. EntityFramework Core如何映射动态模型?

    前言 本文我们来探讨下映射动态模型的几种方式,相信一部分童鞋项目有这样的需求,比如每天/每小时等生成一张表,此种动态模型映射非常常见,经我摸索,这里给出每一步详细思路,希望能帮助到没有任何头绪的童鞋, ...

  4. EntityFramework Core依赖注入上下文方式不同造成内存泄漏了解一下?

    前言 这个问题从未遇见过,是一位前辈问我EF Core内存泄漏问题时我才去深入探讨这个问题,刚开始我比较惊讶,居然还有这种问题,然后就有了本文,直接拿前辈的示例代码并稍加修改成就了此文,希望对在自学E ...

  5. 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平台下, ...

  6. 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 ...

  7. EntityFramework Core 2.0执行原始查询如何防止SQL注入?

    前言 接下来一段时间我们来讲讲EntityFramework Core基础,精简的内容,深入浅出,希望为想学习EntityFramework Core的童鞋提供一点帮助. EntityFramewor ...

  8. 浅谈.net core如何使用EFCore为一个上下文注类型注入多个实例用于连接主从数据库

    在很多一主多从数据库的场景下,很多开发同学为了复用DbContext往往采用创建一个包含所有DbSet<Model>父类通过继承派生出Write和ReadOnly类型来实现,其实可以通过命 ...

  9. Asp.Net Core 项目实战之权限管理系统(3) 通过EntityFramework Core使用PostgreSQL

    0 Asp.Net Core 项目实战之权限管理系统(0) 无中生有 1 Asp.Net Core 项目实战之权限管理系统(1) 使用AdminLTE搭建前端 2 Asp.Net Core 项目实战之 ...

随机推荐

  1. 基于微信小程序的租房小程序

    乐直租全国租房小程序前端 房源分钟上传,可快捷联系房东的小程序. 该小程序操作简单,布局清新,欢迎 start ~ 传送门:Github 扫码体验: pages: 首页 index 选择发布页 bef ...

  2. ggplot2(1) 简介

    1.1 简介 ggplot2是一个用来绘制统计图形(数据图形)的R软件包,与其他大多数的图形软件包不同,ggplot2是由其背后的一套图形语法所支持的.ggplot2可以绘制出很多美观度的图形,同时能 ...

  3. Python多线程的事件监控

    设想这样一个场景: 你创建了10个子线程,每个子线程分别爬一个网站,一开始所有子线程都是阻塞等待.一旦某个事件发生:例如有人在网页上点了一个按钮,或者某人在命令行输入了一个命令,10个爬虫同时开始工作 ...

  4. Java 创建、编辑、删除Excel命名区域

    Excel命名区域,即对指定单元格区域进行命名,以便对单元格区域引用,如在公式运用中可以引用指定命名区域进行公式操作.在创建命名区域时,可针对整个工作簿来创建,即workbook.getNameRan ...

  5. treeselect 异步拿到数据后 要经过一个过滤 然后放入options 否则组件会崩溃

    事件缘由:由于城市树会反复调用,所以不用api每次调用,改用数据缓存一次读取,各处使用.然后我把调用的数据放入treeselect,发现组件就崩溃了,但是api读取的数据就没问题. 查看不是props ...

  6. express第三方中间件研究之bodyParser中间件

    转载至:http://www.cnblogs.com/lianer/p/5178693.html 接触nodejs已有一段时间了,但最近才开始落实项目,于是使用express应用生成器生成了一个应用. ...

  7. 2000字读懂Java的来源

    title: Java的来源 blog: CSDN data: Java学习路线及视频 1.Java市场 截至2020-03: Java的市场份额为17.78% 2. Java语言简介 是SUN(St ...

  8. 环境篇:VMware Workstation安装Centos7

    环境篇:VMware Workstation安装Centos7 1 VMware Workstation安装 CentOS下载地址:http://isoredirect.centos.org/cent ...

  9. C# 在DataGridView中,点击单元格调出 TreeView控件 或 ListBox控件

    1.调出 TreeView控件 或  ListBox控件 private void deductGrid1_CellClick(object sender, DataGridViewCellEvent ...

  10. MySQL学习(4)

    一 视图 预先定义一种对应关系,如:temp_table <-----> select * from class where student_id >10,那么这种对应关系叫做视图. ...