浅析 EF Core 5 中的 DbContextFactory
EF Core 5 中的 DbContextFactory
Intro
使用過 EF Core 大多都會遇到這樣一個(gè)場景,希望能夠并行查詢,但是如果使用同一個(gè) DbContext 實(shí)例進(jìn)行并行操作的時(shí)候就會遇到一個(gè) InvalidOperationException 的異常,在 EF Core 2.x/3.x 版本中, EF Core DbContext 的生命周期默認(rèn)是 Scoped,如果要并行查詢,需要?jiǎng)?chuàng)建多個(gè) Scope,在子 Scope 中創(chuàng)建 DbContext 來進(jìn)行操作,EF Core 5 中的 DbContextFactory 可以用來簡化這樣的操作,且看下文示例
DbContextFactory
DbContextFactory 就如同它的名字一樣,就是一個(gè) DbContext 的工廠,就是用來創(chuàng)建 DbContext 的
IDbContextFactory 接口定義如下,Github 源碼 https://github.com/dotnet/efcore/blob/v5.0.0/src/EFCore/IDbContextFactory.cs
public?interface?IDbContextFactory<out?TContext>?where?TContext?:?DbContext {///?<summary>///?????<para>///?????????Creates?a?new?<see?cref="DbContext"?/>?instance.///?????</para>///?????<para>///?????????The?caller?is?responsible?for?disposing?the?context;?it?will?not?be?disposed?by?the?dependency?injection?container.///?????</para>///?</summary>///?<returns>?A?new?context?instance.?</returns>TContext?CreateDbContext(); }需要注意的是,如果使用 DbContextFactory 來創(chuàng)建 DbContext,需要自己來釋放 DbContext,需要自己使用 using 或者 Dispose 來釋放資源
另外 DbContextFactory 生命周期不同于 DbContext,默認(rèn)的生命周期的 Singleton,也正是因?yàn)檫@樣使得我們可以簡化并行查詢的代碼,可以參考
https://github.com/dotnet/efcore/blob/v5.0.0/src/EFCore/Extensions/EntityFrameworkServiceCollectionExtensions.cs#L607
Sample
來看一個(gè)實(shí)際的示例,這是一個(gè)并行操作插入100條記錄的簡單示例,看一下如何使用 DbContextFactory 進(jìn)行并行操作
var?services?=?new?ServiceCollection(); services.AddDbContextFactory<TestDbContext>(options?=> {options.UseInMemoryDatabase("Tests"); }); using?var?provider?=?services.BuildServiceProvider(); var?contextFactory?=?provider.GetRequiredService<IDbContextFactory<TestDbContext>>();Enumerable.Range(1,?100).Select(async?i?=>{using?(var?dbContext?=?contextFactory.CreateDbContext()){dbContext.Posts.Add(new?Post()?{?Id?=?i?+?101,?Author?=?$"author_{i}",?Title?=?$"title_{i}"?});return?await?dbContext.SaveChangesAsync();}}).WhenAll().Wait();using?var?context?=?contextFactory.CreateDbContext(); Console.WriteLine(context.Posts.Count());實(shí)現(xiàn)源碼
EF Core 的 DbContextFactory 的實(shí)現(xiàn)不算復(fù)雜,一起來看一下,首先看一下 DbContextFactory 的實(shí)現(xiàn):
public?class?DbContextFactory<TContext>?:?IDbContextFactory<TContext>?where?TContext?:?DbContext {private?readonly?IServiceProvider?_serviceProvider;private?readonly?DbContextOptions<TContext>?_options;private?readonly?Func<IServiceProvider,?DbContextOptions<TContext>,?TContext>?_factory;public?DbContextFactory([NotNull]?IServiceProvider?serviceProvider,[NotNull]?DbContextOptions<TContext>?options,[NotNull]?IDbContextFactorySource<TContext>?factorySource){Check.NotNull(serviceProvider,?nameof(serviceProvider));Check.NotNull(options,?nameof(options));Check.NotNull(factorySource,?nameof(factorySource));_serviceProvider?=?serviceProvider;_options?=?options;_factory?=?factorySource.Factory;}public?virtual?TContext?CreateDbContext()=>?_factory(_serviceProvider,?_options); }可以看到 DbContextFactory 的實(shí)現(xiàn)里用到了一個(gè) IDbContextFactorySource,再來看一下 DbContextFactorySource 的實(shí)現(xiàn),實(shí)現(xiàn)如下:
public?class?DbContextFactorySource<TContext>?:?IDbContextFactorySource<TContext>?where?TContext?:?DbContext {public?DbContextFactorySource()=>?Factory?=?CreateActivator();public?virtual?Func<IServiceProvider,?DbContextOptions<TContext>,?TContext>?Factory?{?get;?}private?static?Func<IServiceProvider,?DbContextOptions<TContext>,?TContext>?CreateActivator(){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){var?isGeneric?=?parameters[0].ParameterType?==?typeof(DbContextOptions<TContext>);if?(isGeneric||?parameters[0].ParameterType?==?typeof(DbContextOptions)){var?optionsParam?=?Expression.Parameter(typeof(DbContextOptions<TContext>),?"options");var?providerParam?=?Expression.Parameter(typeof(IServiceProvider),?"provider");return?Expression.Lambda<Func<IServiceProvider,?DbContextOptions<TContext>,?TContext>>(Expression.New(constructors[0],isGeneric??optionsParam:?(Expression)Expression.Convert(optionsParam,?typeof(DbContextOptions))),providerParam,?optionsParam).Compile();}}}var?factory?=?ActivatorUtilities.CreateFactory(typeof(TContext),?new?Type[0]);return?(p,?_)?=>?(TContext)factory(p,?null);} }從上面的源碼中可以看得出來, DbContextFactory 把工廠拆成了兩部分,DbContextFactorySource 提供一個(gè)工廠方法,提供一個(gè)委托來創(chuàng)建 DbContext,而 DbContextFactory 則利用 DbContextFactorySource 提供的工廠方法來創(chuàng)建 DbContext.
More
DbContextFactory 可以使得在并行操作的時(shí)候會更加方便一些,但是注意要自己控制好 DbContext 生命周期,防止內(nèi)存泄漏。
對于 EF Core ?DbContextFactory 的實(shí)現(xiàn),不得不說這樣的實(shí)現(xiàn)靈活性更強(qiáng)一些,但是又感覺有一些多余,想要擴(kuò)展 DbContextFactory 的實(shí)現(xiàn),直接重寫一個(gè) DbContextFactory 的實(shí)現(xiàn)服務(wù)注冊的時(shí)候注入就可以了,你覺得呢~~
Reference
https://github.com/dotnet/efcore/blob/v5.0.0/src/EFCore/IDbContextFactory.cs
https://github.com/dotnet/efcore/blob/v5.0.0/src/EFCore/Extensions/EntityFrameworkServiceCollectionExtensions.cs#L607
https://github.com/dotnet/efcore/blob/v5.0.0/src/EFCore/Internal/DbContextFactory.cs
https://github.com/dotnet/efcore/blob/v5.0.0/src/EFCore/Internal/DbContextFactorySource.cs
https://github.com/WeihanLi/SamplesInPractice/blob/master/EF5Samples/DbContextFactoryTest.cs
總結(jié)
以上是生活随笔為你收集整理的浅析 EF Core 5 中的 DbContextFactory的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 听说容器正在吃掉整个软件世界?
- 下一篇: Wifi6网络