- A+
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
,也正是因为这样使得我们可以简化并行查询的代码,可以参考
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