Blazor Server:混合使用EF Core DbContextFactory和DbContext

11
我正在构建一个Blazor服务器前端,用于现有的领域层。该层提供各种可注入服务来对EF Core存储库进行修改。为此,这些服务本身会从(标准的Microsoft)DI容器中请求一个DbContext。这在具有作用域DbContext实例的常规MVC.NET/Razor页面中可以正常工作,但是如所述,在Blazor中存在问题。在Blazor Server应用程序中,我们希望使用DbContextFactory生成用于操作的短暂DbContext实例。
在同一应用程序中拥有DbContext和DbContextFactory没有问题,但我正在努力理解如何调整我的服务,或者是否需要这样做?为了说明问题,这是当前的代码:
我的页面:
@page “/items”
@inject ItemService ItemService

// somewhere in the code
    ItemService.DoOperation(…)

我的服务

class ItemService
{
    public ItemService(MyDbContext myDbContext)
    {
        …
    }

    public bool DoOperation()
    {
        …
        _myDbContext.SaveChanges();
    }
}

Startup.cs:

            services.AddDbContext<MyDbContext>(options => …),
                contextLifetime: ServiceLifetime.Transient,
                optionsLifetime: ServiceLifetime.Singleton
                );

            services.AddDbContextFactory<MyDbContext>(options => …);

我根据这个答案中提供的示例,已经更改了DbContext的生命周期,并且到目前为止,我还没有遇到任何问题,但是我不完全理解这里涉及的生命周期问题。我该如何设计我的服务,以明显的方式在Blazor和MVC/Razor Pages应用程序中使用?


3
只是一个更新:在 EF Core 6 中,AddDbContextFactory 现在也注册 DbContext,因此不再需要单独注入它们。 - Martijn
3个回答

7
在典型的MVC应用程序中,一个请求代表一个单独的工作单元。DbContext会生成为一个范围服务,并通过构造函数进行注入。这是可以的。
另一方面,在Blazor Server中,一个请求不再代表一个单独的工作单元。第一个请求创建了一个电路,这意味着任何被注入的范围服务都将具有如此描述 此处 的生存期。
在Blazor Server应用程序中,工作单元是一个SignalR消息。(例如,添加新行到数据库的按钮单击)。因此,直接注入上下文 不是正确的方法
这就是为什么Blazor Server拥有IDbContextFactory的原因。像这样初始化它:
services.AddDbContextFactory<DbContext>(options =>
                options.UseSqlServer(
                    Configuration.GetConnectionString("WebDB")));

在与Blazor应用程序相关的Razor组件中,您可以像这样使用它:
private readonly IDbContextFactory<DbContext> factory;

public Component(IDbContextFactory<DbContext> f)
{
    factory = f;
}

public void Click()
{
    using(DbContext cnt = factory.CreateDbContext())
    {
    // Your code here
    }
}

更详细的解释可以在这里找到。

文档:使用Entity Framework Core(EFCore)的ASP.NET Core Blazor Server,点击此处


4
是的,所以现在我正在为从Blazor直接访问EF Core而做的事情。问题在于我使用的现有服务当前注入的是DbContext而不是DbContextFactory。这必须是一个常见的问题,但我看到没有人谈论它。 - Martijn
【礼貌】为什么要通过组件直接从EF访问数据库?这会将组件与Blazor Server绑定在一起,在我的(个人)设计书中是不可取的。我知道MS Docs链接显示了这样做,但是... - MrC aka Shaun Curtis
@MrCakaShaunCurtis 我只是展示官方文档。个人而言,我会为数据访问创建一个单独的服务,并将其注入到需要它的任何地方。总的来说,开发人员可以使用任何方法。 - Grizzlly
@Grizzlly - 很高兴听到这个好消息。 :-) - MrC aka Shaun Curtis
@martijn,使用服务的正确方式是什么? - Sebastian
显示剩余3条评论

1
问题是传递性的:您的服务依赖于一个应该被限定范围的资源,只有当您将这些服务注册为作用域时才能正常工作。但您无法这样做。
正确的方法是将您的服务重写为每个操作模型的DbContext,并注入DbContextFactory。
看起来您已经有一个混合模型(每个操作都有一个SaveChanges,实际上它们是一个UoW)。
如果您不想进行这些更改,您可以通过将DbContext注册为瞬态来规避问题。虽然这感觉不好,但它旨在快速释放底层连接。因此,它并不像它看起来那样存在资源泄漏。

0

我的实践:设置DbContext使用的数据库类型

services.AddScoped<AuditableEntitySaveChangesInterceptor>();
    if (configuration.GetValue<bool>("UseInMemoryDatabase"))
    {
        services.AddDbContext<ApplicationDbContext>(options =>
        {
            options.UseInMemoryDatabase("BlazorDashboardDb");
            options.EnableSensitiveDataLogging();
        });
    }
    else
    {
        services.AddDbContext<ApplicationDbContext>(options =>
        {
            options.UseSqlServer(
                  configuration.GetConnectionString("DefaultConnection"),
                  builder =>
                  {
                      builder.MigrationsAssembly(typeof(ApplicationDbContext).Assembly.FullName);
                      builder.EnableRetryOnFailure(maxRetryCount: 5,
                                                   maxRetryDelay: TimeSpan.FromSeconds(10),
                                                   errorNumbersToAdd: null);
                      builder.CommandTimeout(15);
                  });
            options.EnableDetailedErrors(detailedErrorsEnabled: true);
            options.EnableSensitiveDataLogging();
        });
        services.AddDatabaseDeveloperPageExceptionFilter();
    }

正如我们所知,Blazor Server 中的 DbContext 应该是短暂生命周期。

 services.AddTransient<IDbContextFactory<ApplicationDbContext>, BlazorContextFactory<ApplicationDbContext>>();
 services.AddTransient<IApplicationDbContext>(provider => provider.GetRequiredService<IDbContextFactory<ApplicationDbContext>>().CreateDbContext());

例如,不更改使用DbContext:

 public class AddEditCustomerCommandHandler : IRequestHandler<AddEditCustomerCommand, Result<int>>
{
    private readonly IApplicationDbContext _context;
    private readonly IMapper _mapper;
    private readonly IStringLocalizer<AddEditCustomerCommandHandler> _localizer;
    public AddEditCustomerCommandHandler(
        IApplicationDbContext context,
        IStringLocalizer<AddEditCustomerCommandHandler> localizer,
        IMapper mapper
        )
    {
        _context = context;
        _localizer = localizer;
        _mapper = mapper;
    }
    }

什么是BlazorContextFactory,这行代码的作用是什么: services.AddTransient<IDbContextFactory<ApplicationDbContext>, BlazorContextFactory<ApplicationDbContext>>(); - Kirsten

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接