EntityFramework Core 3.x上下文构造函数可以注入实例呢?
今天討論的話題來自一位微信好友遇到問題后請求我的幫助,當(dāng)然他的意圖并不是本文標(biāo)題,只是我將其根本原因進(jìn)行了一個概括,接下來我們一起來探索標(biāo)題的問號最終的答案是怎樣的呢?老規(guī)矩,首先我們定義如下上下文
public?class?EFCoreDbContext?:?DbContext {public?EFCoreDbContext(DbContextOptions<EFCoreDbContext>?options)?:?base(options){} }接下來在Web應(yīng)用程序中如下注入該上下文實例,然后我們就可以開心的玩耍了
services.AddDbContext<EFCoreDbContext>(options?=> {options.UseSqlServer(@"Server=.;Database=EFCoreTest;Trusted_Connection=True;"); });問題來了,這位童鞋說,我想要在上述上下文中注入一個實例,當(dāng)時聽到這種情況還比較驚訝,什么情況下才會在上下文構(gòu)造函數(shù)中注入實例呢?我們先不關(guān)心這個問題,那還不好說,和正常在ASP.NET Core中使用不就完事了么,實踐是檢驗真理的唯一標(biāo)準(zhǔn),我們來試試,定義如下接口:
接下來則是注入該接口,如下:
services.AddScoped<IHello,?Hello>();然后就來到上下文構(gòu)造函數(shù)中使用該接口,我們搞個方法來測試下看看,如下:
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();} }最后我們在控制器中使用上下文并調(diào)用上述方法,看看是否可行
[ApiController] [Route("[controller]")] public?class?WeatherForecastController?:?ControllerBase {private?readonly?EFCoreDbContext?_context;public?WeatherForecastController(EFCoreDbContext?context){_context?=?context;}[HttpGet]public?string?Get(){return?_context.Print();} }呀,沒毛病啊,自我感覺甚是良好,莫慌,這位童鞋說這樣操作沒問題啊,但是我想將上下文注入為實例池的方式,結(jié)果卻不行,會拋出異常,到底啥異常啊,如下我們修改成實例池的方式瞧瞧:
services.AddDbContextPool<EFCoreDbContext>(options?=> {options.UseSqlServer(@"Server=.;Database=EFCoreTest;Trusted_Connection=True;"); });大意為因為該上下文沒有只有單個參數(shù)是DbContextOptions的構(gòu)造函數(shù),所以該上下文不能被池化,說明構(gòu)造函數(shù)只能有一個包含DbContextOptions的參數(shù),否則報錯,我們還是看看源碼中到底是如何實例化實例池的呢?
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; }上述對于實例池是通過表達(dá)式來構(gòu)建的實例池,但是在此之前會做一步驗證構(gòu)造函數(shù)參數(shù)只能有一個且為DbContextOptions,否則將拋出異常,為何要如此設(shè)計呢?我們再來看看在調(diào)用上下文實例池到底做了什么呢?如下我只列舉出關(guān)鍵信息:
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);......????}原來在調(diào)用實例池時,添加的所以內(nèi)部服務(wù)都是單例,所以我們可以大膽得出結(jié)論:在注入上下文實例池時,添加的內(nèi)部核心服務(wù)是單例,而我們注入的實例可能為其他類型,所以EntityFramework Core做了限定,構(gòu)造函數(shù)只能包含DbContextOptions。那么我們在上下文中怎樣才能使用我們注入的實例呢?其實EntityFramework Core考慮到有這樣的需求,所以給出了對應(yīng)解決方案,在上下文中存在GetService方法,是不是很熟悉,不過需要我們手動導(dǎo)入命名空間,直接在對應(yīng)方法中獲取注入的實例,這樣就繞過了上下文構(gòu)造函數(shù),如下:
public?string?Print() {return?this.GetService<IHello>().Say(); }哎呀,本以為找到了良藥,結(jié)果又報錯了,這是為何呢?要是我們將注入的實例修改為單例結(jié)果將是好使的,我已經(jīng)親自驗證過,這里就不再浪費篇幅,根本原因在哪里呢?此時我們再來看看上述GetService的實現(xiàn)是怎樣的呢?
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; }是否有種恍然大悟的感覺,這里做了判斷,因為在注入上下文實例池時,也注入了核心服務(wù)且為單例,但是我們在startup中注入的實例有可能不是單例,比如為scope時,此時會將我們注入的實例通過GetService獲取時作為內(nèi)部服務(wù),所以會出現(xiàn)無法解析的情況并拋出異常,所以為了解決這個問題,我們必須明確告訴EF Core對于哪些ServiceProvider使用內(nèi)部服務(wù),除此之外,將通過上述ApplicationServiceProvider來獲取而不包括內(nèi)部服務(wù),將內(nèi)部服務(wù)和外部服務(wù)做一個明確的區(qū)分即可,在EntityFramework Core中對于內(nèi)部服務(wù)的注冊,已經(jīng)通過擴(kuò)展方法進(jìn)行了封裝,我們只需手動調(diào)用即可,最終解決方案如下:
//手動注冊針對SQL?Server的內(nèi)部服務(wù) services.AddEntityFrameworkSqlServer();//內(nèi)部服務(wù)使用對應(yīng)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版本也同樣適用,所以不要認(rèn)為直接通過GetService沒拋出異常而認(rèn)為一切正常,瞎貓碰上死耗子,正是恰好碰到注入的實例為單例而繞過了異常的出現(xiàn),所以上下構(gòu)造函數(shù)可以注入實例嗎,答案是不一定,若為實例池肯定不行,希望通過本文的詳細(xì)描述能給需要在上下文構(gòu)造函數(shù)中注入實例的童鞋一點力所能及的幫助,探究其問題的本質(zhì)才能有所成長,感謝您的閱讀。?
總結(jié)
以上是生活随笔為你收集整理的EntityFramework Core 3.x上下文构造函数可以注入实例呢?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 想基于K8s按需扩展应用程序,可从这几方
- 下一篇: IO 模型知多少