一文读懂EF Core5中DbContextFactory

dotNET全栈开发

共 5542字,需浏览 12分钟

 ·

2020-11-22 12:18

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 TContextwhere TContext : DbContext
{
    /// 
    ///     
    ///         Creates a new  instance.
    ///     
    ///     
    ///         The caller is responsible for disposing the context; it will not be disposed by the dependency injection container.
    ///     
    /// 

    ///  A new context instance. 
    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(options =>
{
    options.UseInMemoryDatabase("Tests")
        ;
});
using var provider = services.BuildServiceProvider();
var contextFactory = provider.GetRequiredService>();

Enumerable.Range(1100)
    .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<TContextwhere TContext : DbContext
{
    private readonly IServiceProvider _serviceProvider;
    private readonly DbContextOptions _options;
    private readonly Func, TContext> _factory;

    public DbContextFactory(
        [NotNull] IServiceProvider serviceProvider,
        [NotNull] DbContextOptions options,
        [NotNull] IDbContextFactorySource 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<TContextwhere TContext : DbContext
{
    public DbContextFactorySource()
        => Factory = CreateActivator();

    public virtual Func, TContext> Factory { get; }

    private static Func, 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);
                if (isGeneric
                    || parameters[0].ParameterType == typeof(DbContextOptions))
                {
                    var optionsParam = Expression.Parameter(typeof(DbContextOptions), "options");
                    var providerParam = Expression.Parameter(typeof(IServiceProvider), "provider");

                    return Expression.Lambda, 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







回复 【关闭】广
回复 【实战】获取20套实战源码
回复 【被删】
回复 【访客】访
回复 【小程序】学获取15套【入门+实战+赚钱】小程序源码
回复 【python】学微获取全套0基础Python知识手册
回复 【2019】获取2019 .NET 开发者峰会资料PPT
回复 【加群】加入dotnet微信交流群

最近超火的微信表情辫子!到底是怎么回事?


副业刚需,没有人能拒绝这个网站!


浏览 57
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报