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

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

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 2.0执行原始查询如何防止SQL注入?

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

  5. EntityFramework Core解决并发详解

    前言 对过年已经无感,不过还是有很多闲暇时间来学学东西和多陪陪爸妈,这一点是极好的,好了,本节我们来讲讲EntityFramework Core中的并发问题. 话题(EntityFramework C ...

  6. EntityFramework Core 2.0自定义标量函数两种方式

    前言 上一节我们讲完原始查询如何防止SQL注入问题同时并提供了几种方式.本节我们继续来讲讲EF Core 2.0中的新特性自定义标量函数. 自定义标量函数两种方式 在EF Core 2.0中我们可以将 ...

  7. EntityFramework Core 1.1有哪些新特性呢?我们需要知道

    前言 在项目中用到EntityFramework Core都是现学现用,及时发现问题及时测试,私下利用休闲时间也会去学习其他未曾遇到过或者用过的特性,本节我们来讲讲在EntityFramework C ...

  8. EntityFramework Core 健康检查

    [导读].NET Core提供对应方法可进行健康检查,那么在EF Core中是否也提供了相应的方式呢? EF Core 2.2+(包含2.2)版本提供了针对上下文的健康检查,接下来我们直接利用.NET ...

  9. EntityFramework Core 3.x添加查询提示(NOLOCK)

    前几天看到有博客园中有园友写了一篇关于添加NOLOCK查询提示的博文,这里呢,我将介绍另外一种添加查询提示的方法,此方式源于我看过源码后的实现,孰好孰歹,请自行判之,接下来我们一起来看看. 在Enti ...

最新文章

  1. 5G NR — 密集组网和异构组网
  2. linux虚拟网络设备--eth, tap/tun, veth-pair(九)
  3. 【AWR】调整AWR数据采样时间间隔及历史快照保留时间
  4. python程序中怎样数个数_python3中的代码行数是怎么计算的?
  5. 【VMCloud云平台】拥抱Docker(六)关于DockerFile(1)
  6. Waymo无人驾驶出租车上线:科技感爆棚,还比Uber便宜
  7. 【CSP201312-4】有趣的数(数位DP)
  8. 话说软件详细设计工具
  9. Linux内核源代码 学习笔记
  10. C++ STL详解超全总结(快速入门STL)
  11. 前端开发找实习宝贵经验总结
  12. Mozilla5.0的含义
  13. 该死的clear 根本不释放内存,怎么才能释放泛型LIST的内存?
  14. 【Win10电脑更新】Win10电脑更新后小娜Cortana不能登录、咨询和兴趣不能查看的问题怎么解决
  15. shareX截图工具提示:shareX\Tools\ffmpeg.exe不存在。解决方案2020年
  16. 巴旦木即将成为农业的下一个“风口”河南巴旦木生态农业:值得期待
  17. linux底层把值传给上层,Android上层如何调用一个底层函数
  18. Tiger DAO VC:将你的风险投资变成DAO组织协同
  19. Visual Studio Code(VS)
  20. 关于OLEDB参数化查询【.net】

热门文章

  1. AJAX+json+jquery实现预加载瀑布流布局
  2. matlab练习程序(PCASVD)
  3. event.x,event.clientX,event.offsetX区别
  4. Zend_Feed 的项目实际应用
  5. 【C】C语言结构体指针的语法
  6. zookeeper学习03 使用场景
  7. 设计模式之代理模式(上) 静态代理与JDK动态代理
  8. APP测试流程和测试点
  9. vb.net2.0 Hmac-md5加密算法
  10. Object C学习笔记11-数组