当前位置: 首页 > news >正文

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.taodudu.cc/news/show-911427.html

相关文章:

  • user32.dll怎么修复?几种修复user32.dll丢失的详细步骤
  • 【ARM 嵌入式 C 文件操作系列 20.4 -- 打印 uint64_t 类型的数值】
  • 一文读懂通用漏洞评分系统CVSS4.0:顺带理清CVE、CWE及其与CVSS之间的关系
  • Sql Server 2016数据库定时备份
  • 数据库数据恢复—SQL Server数据库ndf文件变为0KB的数据恢复案例
  • 今日总结2024.5.14
  • IO 模型知多少
  • 让 .NET 轻松构建中间件模式代码
  • 从编码层面对比java和c#
  • 红帽借“订阅”模式成开源一哥,首创者升任总裁
  • C#两大知名Redis客户端连接哨兵集群的姿势
  • dotNET Core 3.X 请求处理管道和中间件的理解
  • 了解.NET中的垃圾回收
  • 数字化演化历史
  • 如何查找,修复和避免C#.NET中内存泄漏的8个最佳实践
  • ASP.NET Core技术研究-探秘依赖注入框架
  • 从项目到产品: 软件时代需要价值流架构师 | IDCF
  • 推荐一个集录屏、截图、音频于一体的软件给大家
  • WebAssembly增加Go语言绑定
  • .NET中的内存管理
  • .Net微服务实战之技术架构分层篇
  • .NET 下基于动态代理的 AOP 框架实现揭秘
  • Blazor WebAssembly 3.2.0 Preview 4 如期发布
  • C#/.Net Core/WPF框架初建(国际化、主题色)
  • ASP.NET Core 日志框架:Serilog
  • 树莓派销量突然猛增
  • C#黔驴技巧之实现统计结果排名
  • Istio Pilot架构解析
  • 当模板方法遇到了委托函数,你的代码又可以精简了
  • 为什么要用内插字符串代替string.format
  • iPhone上运行Linux也要来了
  • 谁说.NET不适合搞大数据、机器学习和人工智能
  • .NET Core技术研究-主机
  • 多角度让你彻底明白yield语法糖的用法和原理及在C#函数式编程中的作用
  • 哪种开源许可证最适合商业化?
  • 还不会docker+k8s?2020年,就要面对现实了...