前言
上一篇我们简单讲述了在EF Core1.1中如何进行迁移,本文我们来讲讲EF Core1.1中那些不为人知的事,细抠细节,从我做起。
显式创建DbContext实例
通过带OnConfiguring的构造函数
这个想必是我们最简单的方式了吧,通过调用继承自DbContext的类并且调用它的无参构造函数,同时我们需要谨记的时每当实例化时我们都需要将其释放也就是将其实例包裹在Using中。如下:
using (var context = new EFCoreContext()) { }
接着通过重载OnConfiguring来配置EF Core上下文实例,如下。
public class EFCoreContext: DbContext { protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseSqlServer(@"Server=.;Database=EFTest;Trusted_Connection=True;"); }
【注意】:重载OnConfiguring和之前EF版本中的OnModelCreating创建模型不一样,OnModelCreating创建模型上下文只实例化一次,但是OnConfiguring每实例化一个上下文时都会被调用一次,所以OnConfiguring能充分利用上下文中的构造函数或者其他数据。
在EF 6.x中对于上下文有许多构造函数,例如连接字符串传参,在EF Core 1.1中也是可以的如下:
public class EFCoreContext: DbContext { private readonly string _connectionString; public EFCoreContext(string connectionString) { _connectionString = connectionString; } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) => optionsBuilder.UseSqlServer(_connectionString); }
使用不带依赖注入的DbContextOptions
在DbContext的构造函数中我们可以接受一个DbContextOptions对象,这个主要用在当在DI容器中创建DbContext实例时会用到,当然它也能被显式调用,通过创建DbCOntextOptions对象来与上下文隔离,所以用它可以为每一个上下文的实例使用相同的options,如下:
public class EFCoreContext : DbContext { public EFCoreContext(DbContextOptions options) : base(options) { } }
public class HomeController : Controller { private static DbContextOptions _contextOptions;
public IActionResult Index() { _contextOptions = new DbContextOptionsBuilder() .UseSqlServer("") .Options; using (var context = new EFCoreContext(_contextOptions)) { } return View(); } }
看到这里我们看到确确实实不再需要重载OnConfiguring,但是OnConfiguring将还是会被一直重载和调用,为什么会这样尼,因为我们在配置中注入上下文它会调用构造函数并同时来对OnConfiguring进行适当的调整。
使用依赖注入创建DbContext实例
我们只要在DI容器中注册上下文类型,然后它将被DI容器所解析,但是将上下文注册到DI容器中我们就不用管了吗,你还得注意到以下两点。我们一般将其注入到DI容器中是这样做的。
public class EFCoreContext : DbContext { public EFCoreContext(DbContextOptions options) : base(options) { } }
services.AddDbContext<EFCoreContext>(options => { options.UseSqlServer(sqlStr, d => d.MigrationsAssembly("StudyEFCore")); });
合情合理合法,但是为什么不能如下这样用尼
services.AddSingleton<EFCoreContext>();
利用单例的形式注入难道就不行么,如果你这样做了,你就等着程序崩溃吧,因为上下文DbContext不是线程安全的,也就是说不能被注册到单例来使用没有额外的锁。接着就是在将其注入到DI容器后,当我们使用时还是用Using来包裹,你别以为注入到DI容器中就万事大吉它替你什么都做了,DI容器不会替你自动处理,当然了,如果你只是暂时在DI容器中使用的话,通常不会发生灾难。所以我们需要谨记如下两点。
(1)将上下文注入到DI容器中后,当使用DbContext依然还是用Using包括,因为DI容器不会自动将其释放。
(2)DbContext是非线程安全的,即使是在DI容器中,也无非保证不出问题,大部分情况下是不会有问题的,不要担心。
带有DbContextOptions的DI创建DbContext实例
我们在DI中注册一个DbContextOptions实例还是有点用,它只会被创建一次并作为单例使用。比如我们可以初始化数据啊,如下
public class EFCoreContext : DbContext { public EFCoreContext(DbContextOptions options) : base(options) { } }
private static IServiceProvider _serviceProvider; public IActionResult Index() { var contextOptions = new DbContextOptionsBuilder() .UseSqlServer("") .Options; var services = new ServiceCollection() .AddSingleton(contextOptions) .AddScoped<EFCoreContext>(); _serviceProvider = services.BuildServiceProvider(); return View(); }
现在EFCoreContext上下文将会被DI容器解析,通过将DbContextOptions实例将被注入到它的构造函数中。这个_serviceProvider就是注入上下文的提供者,我们上述也说了可以初始化数据,如何初始化尼,来我们看看。在Startup.cs中有如下配置使用方法:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { .... }
这个app有如下属性:
发现什么没有,这个就是所有注入的服务的抽象属性,我们将其转换成EFCoreContext就可以在这个方法中初始化数据,我们看看。
private static EFCoreContext context; public static void Initialize(IServiceProvider serviceProvider) { context = (EFCoreContext )serviceProvider.GetService(typeof(EFCoreContext )); //do your something }
使用泛型DbContextOptions创建DbContext实例
上述我们介绍的都是非泛型的DbContextOptions对象,如下:
public class EFCoreContext : DbContext { public EFCoreContext(DbContextOptions options) : base(options) { } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.AddEntityConfigurationsFromAssembly(GetType().GetTypeInfo().Assembly); } }
但是其参数中的DbContextOptions还有一个泛型版本,那么泛型是用来干嘛的了,我们先看如下例子:
public class EFCoreContext1 : DbContext { public EFCoreContext1text1(DbContextOptions<EFCoreContext1> options) : base(options) { } } public class EFCoreContext2 : DbContext { public EFCoreContext2(DbContextOptions<EFCoreContext2> options) : base(options) { } } var contextOptions1 = new DbContextOptionsBuilder<EFCoreContext1>() .UseSqlServer(connectionString1) .Options; var contextOptions2 = new DbContextOptionsBuilder<EFCoreContext2>() .UseSqlServer(connectionString2) .Options; var services = new ServiceCollection() .AddSingleton(contextOptions1) .AddScoped<EFCoreContext1>() .AddSingleton(contextOptions2) .AddScoped<EFCoreContext2>(); _serviceProvider = services.BuildServiceProvider();
看到什么没有,如果有多个上下文类型在DI容器中注册时我们可以允许每个上下文的类型都依赖于自己的Options。当解析EFCoreContext1时将会导致DbContextOptions<EFCoreContext1>会被注入,同理当解析EFCoreContext2时将导致DbContextOptions<EFCoreContext2>会被注入。
使用AddDbContext来创建DbContext实例
通过AddDbContext语法糖来注册DbContext和DbContextOptions实例。如下:
var services = new ServiceCollection() .AddDbContext<EFCoreContext>( b => b.UseSqlServer(connectionString));
默认情况下将EFCoreContext作为scope进行注册,将DbContextOptions作为单例进行注册,你可更改将EFCoreContext作为单例注册,上述我们已经讨论过这个问题了啊。好了到了这里想必我们知道创建EF Core上下文实例的几种方式了吧,我们概括为以下三点。
(1)直接调用上下文构造函数并重载OnConfiguring创建上下文实例。
(2)传递DbContextOptions到构造函数中创建上下文实例。
(3)通过DI容器来创建上下文实例。
使用DbContextOptionsBuilder来配置一个DbContext并最终构造一个DbContextOptions对象,这也能够通过重载OnConfiguring方法或者通过构造options来传递到DbContext的构造函数中去,无论是通过无参构造函数还是通过DbContextOptions传递到上下文的构造函数中去,抑或是通过将DbContext注入到DI容器中去都是一样的,都没有什么本质区别,只不过通过DI简便一点而且逼格比较高而已,没有其他。
总结
好了,本节我们对于EF Core 1.1中创建上下文的几种方式都已叙述完毕,你是否已经Understand呢,晚安,世界,又熬夜了。