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 == 1){var parameters = constructors[0].GetParameters();if (parameters.Length == 1&& (parameters[0].ParameterType == typeof(DbContextOptions)|| parameters[0].ParameterType == typeof(DbContextOptions<TContext>))){returnExpression.Lambda<Func<TContext>>(Expression.New(constructors[0], Expression.Constant(options))).Compile();}}return null;
}

上述对于实例池是通过表达式来构建的实例池,但是在此之前会做一步验证构造函数参数只能有一个且为DbContextOptions,否则将抛出异常,为何要如此设计呢?我们再来看看在调用上下文实例池到底做了什么呢?如下我只列举出关键信息:

public static IServiceCollection AddDbContextPool<TContextService, TContextImplementation>([NotNull] this IServiceCollection serviceCollection,[NotNull] Action<IServiceProvider, DbContextOptionsBuilder> optionsAction,int poolSize = 128)where TContextImplementation : DbContext, TContextServicewhere TContextService : class
{AddCoreServices<TContextImplementation>(serviceCollection,(sp, ob) =>{......},ServiceLifetime.Singleton);......    }

原来在调用实例池时,添加的所以内部服务都是单例,所以我们可以大胆得出结论:在注入上下文实例池时,添加的内部核心服务是单例,而我们注入的实例可能为其他类型,所以EntityFramework Core做了限定,构造函数只能包含DbContextOptions。那么我们在上下文中怎样才能使用我们注入的实例呢?其实EntityFramework Core考虑到有这样的需求,所以给出了对应解决方案,在上下文中存在GetService方法,是不是很熟悉,不过需要我们手动导入命名空间,直接在对应方法中获取注入的实例,这样就绕过了上下文构造函数,如下:

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没抛出异常而认为一切正常,瞎猫碰上死耗子,正是恰好碰到注入的实例为单例而绕过了异常的出现,所以上下构造函数可以注入实例吗,答案是不一定,若为实例池肯定不行,希望通过本文的详细描述能给需要在上下文构造函数中注入实例的童鞋一点力所能及的帮助,探究其问题的本质才能有所成长,感谢您的阅读。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310553.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringCloud常见问题总结(一)

Eureka常见问题 Eureka注册服务慢 默认情况&#xff0c;服务注册到Eureka Server 的过程比较慢。在开发或者测试时候&#xff0c;如果能够加速注册的过程&#xff0c;从而提升工作效率。Spring Cloud官方文档详细描述了该问题的原因并提供了解决方案&#xff1a; //原文 Why…

[剑指offer]面试题21:包含min函数的栈

面试题21&#xff1a;包含min函数的栈 题目&#xff1a;定义栈的数据结构&#xff0c;请在该类型中实现一个能够得到栈的最小元素的min函数。在该栈中&#xff0c;调用min、push及pop的时间复杂度都是O&#xff08;1&#xff09;。 栈内压入3、4、2、1之后接连两次弹出栈顶数字…

IO 模型知多少

1. 引言同步异步I/O&#xff0c;阻塞非阻塞I/O是程序员老生常谈的话题了&#xff0c;也是自己一直以来懵懵懂懂的一个话题。比如&#xff1a;何为同步异步&#xff1f;何为阻塞与非阻塞&#xff1f;二者的区别在哪里&#xff1f;阻塞在何处&#xff1f;为什么会有多种IO模型&am…

[剑指offer]面试题22:栈的压入、弹出序列

面试题22&#xff1a;栈的压入、弹出序列 题目&#xff1a;输入两个整数序列&#xff0c;第一个序列表示栈的压入顺序&#xff0c;请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1、2、3、4、5 是某栈的压栈序列&#xff0c;序列4、5、3、2、1…

Ubuntu上安装使用Docker

Docker简介 Docker是一个开源的容器引擎&#xff0c;他有助于更快的交付应用。Docker可以将应用程序和基础设施层做隔离&#xff0c;并且能将基础设施当做程序一样进行管理。使用Docker&#xff0c;可以更快的打包&#xff0c;测试以及部署应用程序&#xff0c;并且可以缩短从…

让 .NET 轻松构建中间件模式代码

让 .NET 轻松构建中间件模式代码Intro在 asp.net core 中中间件的设计令人叹为观止&#xff0c;如此高大上的设计何不集成到自己的代码里呢。于是就有了封装了一个简单通用的中间件模板的想法&#xff0c;以后有需要的时候就可以拿来即用。接口定义这里按执行的委托是同步还是异…

[剑指offer]面试题26:复杂链表的复制

面试题26&#xff1a;复杂链表的复制 题目&#xff1a;请实现函数ComplexListNodeClone&#xff08;ComplexListNodepHead&#xff09;&#xff0c;复制一个复杂链表。在复杂链表中&#xff0c;每个结点除了有一个m_pNext指针指向下一个结点外&#xff0c;还有一个m_pSibling 指…

SpringCloud + Docker

Dockerfile构建Docker 镜像 注意这里说的Dockerfile是指的一个文本文件&#xff0c;类似txt&#xff0c;只不过名字是Dockerfile&#xff0c;里面编辑Docker的一些指令&#xff0c;指令作用在于描述构建镜像的细节。如下一个简单的案例&#xff0c;用上一节中下载的nginx镜像来…

红帽借“订阅”模式成开源一哥,首创者升任总裁

4 月 6 日&#xff0c;红帽公司宣布&#xff0c;产品和技术总裁 Paul Cormier 即日起任红帽总裁&#xff0c;并兼任首席执行官。Paul Cormier 是开源商业化“订阅”模式的提出者&#xff0c;这一模式促使红帽达成连续超70个季度的盈利&#xff0c;身价升至340亿美金&#xff0c…

Docker中数据管理

Docker数据管理 生产环境中&#xff0c;对数据进行持久化&#xff0c;或者需要在多个容器直接进行数据共享&#xff0c;这必然涉及到容器的一些数据管理的操作。容器中数据管理主要有两种方式&#xff1a; 数据卷&#xff08;Data Volumes&#xff09;&#xff1a;容器内数据直…

C#两大知名Redis客户端连接哨兵集群的姿势

前言前面《Docker-Compose搭建Redis高可用哨兵集群》&#xff0c;我的思路是将Redis、Sentinel、Redis Client App链接到同一个网桥网络&#xff0c;这个网桥内的Redis Client App就可以使用ContainerIP访问网桥内任意redis节点。同一网桥网络访问规避了Docker上发生的NAT&…

Docker之Dockerfile详解

使用Dockerfile创建镜像 Dockerfile是一个文本格式的配置文件&#xff0c;我们可以利用Dockerfile来快速的创建一个自定义的镜像。 基本结构 Dockerfile由一行命令语句组成&#xff0c;并且支持以#开头的注释一般包括四个部分&#xff1a;基础镜像信息&#xff0c;维护者信息…

创业的N种死法

点击蓝字关注&#xff0c;回复“职场进阶”获取职场进阶精品资料一份互联网的江湖一直流传着大佬们的创业传奇。马云上厕所几分钟敲定几千万美金融资。王石走投无路靠倒卖玉米赚到上百万从此逆袭。扎克伯格为了获得哈佛美女照片&#xff0c;开发一个小玩意从此改变了世界。传奇…

Docker容器实战思维

Docker成功的基础 Docker的实现用到的基础技术&#xff08;cgroups, namespace&#xff0c;分层文件系统&#xff09;在Docker之前已经存在很多年&#xff0c;并且 Linux Containers&#xff08;LXC&#xff09;也在很多企业的环境中得到了大量的应用实践&#xff0c;并得到明…

dotNET Core 3.X 请求处理管道和中间件的理解

理解 dotNET Core 中的管道模型&#xff0c;对我们学习 dotNET Core 有很大的好处&#xff0c;能让我们知其然&#xff0c;也知其所以然&#xff0c;这样在使用第三方组件或者自己写一些扩展时&#xff0c;可以避免入坑&#xff0c;或者说避免同样的问题多次入坑。本文分为以下…

了解.NET中的垃圾回收

原文来自互联网&#xff0c;由长沙DotNET技术社区编译。尽管这是一篇来自2009年的古老的文章&#xff0c;但或许能够对你理解GC产生一些作用。 了解.NET中的垃圾回收一旦了解了.NET的垃圾收集器是如何工作的&#xff0c;那么可能会触及.NET应用程序的一些更为神秘的问题时&…

数据结构与算法--数组:二维数组中查找

数组 数组最简单的是数据结构&#xff0c;占据一整块连续的内存并按照顺序存储数据&#xff0c;创建数组时候&#xff0c;我们需要首先指定数组的容量大小&#xff0c;然后根据大小分配内存。即使我们只在数组中存储一个元素&#xff0c;亚需要为所有数据预先分配内存&#xf…

数据结构与算法--字符串:字符串替换

数据结构与算法–字符串&#xff1a;字符串替换 字符串的优化 由于字符串在编程时候使用的评率非常高&#xff0c;为了优化&#xff0c;很多语言都对字符串做了特殊的规定。下面我们讨论java中字符串的特性java中的字符数组以’\0’ 结尾&#xff0c;我们可以利用这个特性来找…

数据结构与算法--经典10大排序算法(动图演示)【建议收藏】

十大经典排序算法总结&#xff08;动图演示&#xff09; 算法分类 十大常见排序算法可分为两大类&#xff1a; 比较排序算法&#xff1a;通过比较来决定元素的位置&#xff0c;由于时间复杂度不能突破O(nlogn)&#xff0c;因此也称为非线性时间比较类排序非比较类型排序&…

如何查找,修复和避免C#.NET中内存泄漏的8个最佳实践

原文来自互联网&#xff0c;由长沙DotNET技术社区编译。本文来源&#xff1a;https://michaelscodingspot.com/find-fix-and-avoid-memory-leaks-in-c-net-8-best-practices/从事大型企业项目的任何人都知道内存泄漏就像是大型酒店中的老鼠。当它们很少时&#xff0c;您可能不会…