经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » ASP.net » 查看文章
EFCore 5 中的 DbContextFactory
来源:cnblogs  作者:WeihanLi  时间:2020/11/23 12:26:49  对本文有异议

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

  1. public interface IDbContextFactory<out TContext> where TContext : DbContext
  2. {
  3. /// <summary>
  4. /// <para>
  5. /// Creates a new <see cref="DbContext" /> instance.
  6. /// </para>
  7. /// <para>
  8. /// The caller is responsible for disposing the context; it will not be disposed by the dependency injection container.
  9. /// </para>
  10. /// </summary>
  11. /// <returns> A new context instance. </returns>
  12. TContext CreateDbContext();
  13. }

需要注意的是,如果使用 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 进行并行操作

  1. var services = new ServiceCollection();
  2. services.AddDbContextFactory<TestDbContext>(options =>
  3. {
  4. options.UseInMemoryDatabase("Tests")
  5. ;
  6. });
  7. using var provider = services.BuildServiceProvider();
  8. var contextFactory = provider.GetRequiredService<IDbContextFactory<TestDbContext>>();
  9. Enumerable.Range(1, 100)
  10. .Select(async i =>
  11. {
  12. using (var dbContext = contextFactory.CreateDbContext())
  13. {
  14. dbContext.Posts.Add(new Post() { Id = i + 101, Author = $"author_{i}", Title = $"title_{i}" });
  15. return await dbContext.SaveChangesAsync();
  16. }
  17. })
  18. .WhenAll()
  19. .Wait();
  20. using var context = contextFactory.CreateDbContext();
  21. Console.WriteLine(context.Posts.Count());

实现源码

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

  1. public class DbContextFactory<TContext> : IDbContextFactory<TContext> where TContext : DbContext
  2. {
  3. private readonly IServiceProvider _serviceProvider;
  4. private readonly DbContextOptions<TContext> _options;
  5. private readonly Func<IServiceProvider, DbContextOptions<TContext>, TContext> _factory;
  6. public DbContextFactory(
  7. [NotNull] IServiceProvider serviceProvider,
  8. [NotNull] DbContextOptions<TContext> options,
  9. [NotNull] IDbContextFactorySource<TContext> factorySource)
  10. {
  11. Check.NotNull(serviceProvider, nameof(serviceProvider));
  12. Check.NotNull(options, nameof(options));
  13. Check.NotNull(factorySource, nameof(factorySource));
  14. _serviceProvider = serviceProvider;
  15. _options = options;
  16. _factory = factorySource.Factory;
  17. }
  18. public virtual TContext CreateDbContext()
  19. => _factory(_serviceProvider, _options);
  20. }

可以看到 DbContextFactory 的实现里用到了一个 IDbContextFactorySource,再来看一下 DbContextFactorySource 的实现,实现如下:

  1. public class DbContextFactorySource<TContext> : IDbContextFactorySource<TContext> where TContext : DbContext
  2. {
  3. public DbContextFactorySource()
  4. => Factory = CreateActivator();
  5. public virtual Func<IServiceProvider, DbContextOptions<TContext>, TContext> Factory { get; }
  6. private static Func<IServiceProvider, DbContextOptions<TContext>, TContext> CreateActivator()
  7. {
  8. var constructors
  9. = typeof(TContext).GetTypeInfo().DeclaredConstructors
  10. .Where(c => !c.IsStatic && c.IsPublic)
  11. .ToArray();
  12. if (constructors.Length == 1)
  13. {
  14. var parameters = constructors[0].GetParameters();
  15. if (parameters.Length == 1)
  16. {
  17. var isGeneric = parameters[0].ParameterType == typeof(DbContextOptions<TContext>);
  18. if (isGeneric
  19. || parameters[0].ParameterType == typeof(DbContextOptions))
  20. {
  21. var optionsParam = Expression.Parameter(typeof(DbContextOptions<TContext>), "options");
  22. var providerParam = Expression.Parameter(typeof(IServiceProvider), "provider");
  23. return Expression.Lambda<Func<IServiceProvider, DbContextOptions<TContext>, TContext>>(
  24. Expression.New(
  25. constructors[0],
  26. isGeneric
  27. ? optionsParam
  28. : (Expression)Expression.Convert(optionsParam, typeof(DbContextOptions))),
  29. providerParam, optionsParam)
  30. .Compile();
  31. }
  32. }
  33. }
  34. var factory = ActivatorUtilities.CreateFactory(typeof(TContext), new Type[0]);
  35. return (p, _) => (TContext)factory(p, null);
  36. }
  37. }

从上面的源码中可以看得出来, DbContextFactory 把工厂拆成了两部分,DbContextFactorySource 提供一个工厂方法,提供一个委托来创建 DbContext,而 DbContextFactory 则利用 DbContextFactorySource 提供的工厂方法来创建 DbContext.

More

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

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

Reference

原文链接:http://www.cnblogs.com/weihanli/p/13997751.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号