在Blazor Server中处理EFCore的生命周期,其中后端用于REST API。

huangapple go评论59阅读模式
英文:

Handle EFCore Lifetimes in Blazor Server where Backend is used for REST API as well

问题

我正在使用 Blazor Server,但希望在我的页面上尽量减少业务逻辑,特别是要有选项创建可以处理与我的 Blazor Server 应用程序相同用例的 REST 端点,以便我的 UI 中的加载和保存方法看起来几乎像这样:

private async Task HandleValidSubmit()
{
    if (_myDto != default)
    {
        _myDto.Id = await MyEntityService.Save(_myDto);
    }
}

MyEntityService 中(它位于一个单独的类库中,因此我可以轻松地在将来的 REST API 中使用它),我通过 DI 注入了 MyContext

public class MyEntityService : IMyEntityService
{
    private readonly ILogger<MyEntityService> _logger;
    private readonly IMyContext _context;

    public MyEntityService(ILogger<MyEntityService> logger, IMyContext context)
    {
        _logger = logger;
        _context = context;
    }

    public async Task<int> Save(DtoMyEntity dtoToSave)
    {
        _logger.LogTrace("{method}({dtoToSave})", nameof(Save), dtoToSave);
        //Some magic to convert my DTO to an EF Core entity
        await _context.SaveChangesAsync().ConfigureAwait(false);
        return entity.Id;
    }

}

以下是我用于 DB 上下文的 DI 配置:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddDatabase(this IServiceCollection services)
        => services
            .AddDbContext<IMyContext, MyContext>((provider, options) =>
            {
                options.UseNpgsql(provider.GetRequiredService<IDbConnectionStringHelper>()
                    .GetDbConnectionString("ServiceDatabase"));
                var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
                if (environment == "Development")
                {
                    options.EnableSensitiveDataLogging();
                }
                options.EnableDetailedErrors();
            }, ServiceLifetime.Transient, ServiceLifetime.Transient)
            .AddTransient<IDbConnectionStringHelper, DbConnectionStringHelper>()
    ;
}

现在,如果我连续两次点击“保存”而不重新加载页面/转到另一个页面,然后返回,我会收到以下错误:

实体类型 'MyEntity' 的实例无法被跟踪,因为具有键值 '{Id: 1}' 的另一个实例已经被跟踪。在附加现有实体时,请确保只附加具有给定键值的一个实体实例。

如何解决这个问题?我知道我应该使用 IDbContextFactory,如Microsoft 文档中所述,但这似乎非常特定于 Blazor 服务器。我也希望我的后端类库可以在 REST API 中使用,只使用 Transient 生命周期的上下文是完全可以的。

英文:

I am using Blazor Server but want to have as little business logic as possible on my pages, especially to have the option to create REST endpoints that can handle the same use cases as my Blazor Server app does, so my load and save methods in my UI look pretty much like that:

private async Task HandleValidSubmit()
{
    if (_myDto != default)
    {
        _myDto.Id = await MyEntityService.Save(_myDto);
    }
}

In MyEntityService (which is in a separate class library, so I can easily use it in a future REST API), I then inject MyContext over DI:

public class MyEntityService : IMyEntityService
{
    private readonly ILogger<MyEntityService> _logger;
    private readonly IMyContext _context;

    public MyEntityService(ILogger<MyEntityService> logger, IMyContext context)
    {
        _logger = logger;
        _context = context;
    }

    public async Task<int> Save(DtoMyEntity dtoToSave)
    {
        _logger.LogTrace("{method}({dtoToSave})", nameof(Save), dtoToSave);
        //Some magic to convert my DTO to an EF Core entity
        await _context.SaveChangesAsync().ConfigureAwait(false);
        return entity.Id;
    }

}

Here is my DI configuration for the DB Context:

public static class ServiceCollectionExtensions
{
    public static IServiceCollection AddDatabase(this IServiceCollection services)
        => services
            .AddDbContext<IMyContext, MyContext>((provider, options) =>
            {
                options.UseNpgsql(provider.GetRequiredService<IDbConnectionStringHelper>()
                    .GetDbConnectionString("ServiceDatabase"));
                var environment = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT");
                if (environment == "Development")
                {
                    options.EnableSensitiveDataLogging();
                }
                options.EnableDetailedErrors();
            }, ServiceLifetime.Transient, ServiceLifetime.Transient)
            .AddTransient<IDbConnectionStringHelper, DbConnectionStringHelper>()
    ;
}

When I now click Save two times without reloading the page/going to another page, and coming back, I get the following error:

The instance of entity type 'MyEntity' cannot be tracked because another instance with the key value '{Id: 1}' is already being tracked. When attaching existing entities, ensure that only one entity instance with a given key value is attached.

How can I resolve this issue? I know I should use IDbContextFactory as explained on the Microsoft docs here, but this seems to be very Blazor server specific. I also want my backend class library to be used in a REST API, where using just the context with Transient lifetime is absolutely fine.

答案1

得分: 2

由于Blazor Server不为每个请求创建范围,您必须自行维护它。

public class MyUIHandler
{
    private readonly IServiceProvider _serviceProvider;

    public MyUIHandler(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    private async Task HandleValidSubmit()
    {
        if (_myDto != default)
        {
            using var scope = serviceProvider.CreateScope();
            var myEntityService = scope.ServiceProvider.GetRequiredService<IMyEntityService>();
            _myDto.Id = await myEntityService.Save(_myDto);
        }
    }
}
英文:

Since Blazor Server do not create scope for each request, you have to maintain it by yourself.

public class MyUIHandler
{
    private readonly IServiceProvider _serviceProvider;

    public MyUIHandler(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    private async Task HandleValidSubmit()
    {
        if (_myDto != default)
        {
            using var scope = serviceProvider.CreateScope();
            var myEntityService = scope.ServiceProvider.GetRequiredService&lt;IMyEntityService&gt;();
            _myDto.Id = await myEntityService.Save(_myDto);
        }
    }
}

huangapple
  • 本文由 发表于 2023年7月20日 16:56:00
  • 转载请务必保留本文链接:https://go.coder-hub.com/76728226.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定