zoukankan      html  css  js  c++  java
  • EFCore 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

  • 相关阅读:
    【原创】【js】screenLeft screenTop screenX screenY属性的有效性和兼容性研究
    兼容兼容兼容:浏览器兼容性大集合
    Vue 打包成APP后首屏出现白屏问题
    uniapp 安卓app端本地打包错误: Not found -1,6 at view.umd.min.js:1
    uniapp 下获取cid
    uniapp fill abort statuscode:-1
    Vuex刷新时数据会消失,那如何解决?为什么还要使用Vuex
    vue 安装失败;vue不是内部或外部命令
    js 文件下载,多个文件下载,pdf下载
    uni-App 去掉顶部导航栏
  • 原文地址:https://www.cnblogs.com/weihanli/p/13997751.html
Copyright © 2011-2022 走看看