2020-11-19

EFCore 5 中的 DbContextFactory

EF Core 5 中的 DbContextFactory

EF Core 5 中的 DbContextFactory

Intro

使用过 EF Core 大多都会遇到这样一个场景,希望能够并行查询,但是如果使用同一个 DbContext 实例进行并行操作的时候就会遇到一个 InvalidOperationException 的异常,在 EF Core 2.x/3.x 版本中, EF Core DbContext 的生命周期默认是 Scoped,如果要并行查询,需要创建多个 Scope,在子 Scope 中创建 DbContext 来进行操作,EF Core 5 中的 DbContextFactory 可以用来简化这样的操作,且看下文示例

DbContextFactory

DbContextFactory 就如同它的名字一样,就是一个 DbContext 的工厂,就是用来创建 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 来创建 DbContext,需要自己来释放 DbContext,需要自己使用 using 或者 Dispose 来释放资源

另外 DbContextFactory 生命周期不同于 DbContext,默认的生命周期的 Singleton,也正是因为这样使得我们可以简化并行查询的代码,可以参考

https://github.com/dotnet/efcore/blob/v5.0.0/src/EFCore/Extensions/EntityFrameworkServiceCollectionExtensions.cs#L607

Sample

来看一个实际的示例,这是一个并行操作插入100条记录的简单示例,看一下如何使用 DbContextFactory 进行并行操作

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());

实现源码

EF Core 的 DbContextFactory 的实现不算复杂,一起来看一下,首先看一下 DbContextFactory 的实现:

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 的实现里用到了一个 IDbContextFactorySource,再来看一下 DbContextFactorySource 的实现,实现如下:

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 提供一个工厂方法,提供一个委托来创建 DbContext,而 DbContextFactory 则利用 DbContextFactorySource 提供的工厂方法来创建 DbContext.

More

DbContextFactory 可以使得在并行操作得时候会更加方便一些,但是注意要自己控制好 DbContext 生命周期,防止内存泄漏。

对于 EF Core DbContextFactory 的实现,不得不说这样的实现灵活性更强一些,但是又感觉有一些多余,想要扩展 DbContextFactory 的实现,直接重写一个 DbContextFactory 的实现服务注册的时候注入就可以了,你觉得呢~~

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

原文转载:http://www.shaoqun.com/a/490474.html

百思买:https://www.ikjzd.com/w/394

vincent:https://www.ikjzd.com/w/1642

泛亚班拿:https://www.ikjzd.com/w/1262


EFCore5中的DbContextFactoryEFCore5中的DbContextFactoryIntro使用过EFCore大多都会遇到这样一个场景,希望能够并行查询,但是如果使用同一个DbContext实例进行并行操作的时候就会遇到一个InvalidOperationException的异常,在EFCore2.x/3.x版本中,EFCoreDbContext的生命周期默认是Scoped,如果
c-tick认证:c-tick认证
c88:c88
美国下月将"退群"万国邮联,跨境出口电商该如何应对?:美国下月将"退群"万国邮联,跨境出口电商该如何应对?
京都必去景点,京都有什么好玩的地方:京都必去景点,京都有什么好玩的地方
香港旅游:石板街繁华背后的市井缩影:香港旅游:石板街繁华背后的市井缩影

No comments:

Post a Comment