我正在使用 Blazor Server,但希望页面上的业务逻辑尽可能少,特别是可以选择创建可以处理与我的 Blazor Server 应用程序相同的用例的 REST 端点,因此我的加载和保存方法我的用户界面看起来很像那样:
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;
}
}
这是我的数据库上下文的 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>()
;
}
当我现在单击
Save
两次而不重新加载页面/转到另一个页面并返回时,我收到以下错误:
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.
如何解决这个问题?我知道我应该使用
IDbContextFactory
,如 在此处的 Microsoft 文档 上所解释的那样,但这似乎非常特定于 Blazor 服务器。我还希望我的后端类库在 REST API 中使用,其中仅使用具有 Transient
生命周期的上下文绝对没问题。
由于 Blazor Server 不会为每个请求创建作用域,因此您必须自行维护。
public class MyUIHandler
{
private readonly IServiceScopeFactory _scopeFactory;
public MyUIHandler(IServiceScopeFactory scopeFactory)
{
_scopeFactory = scopeFactory;
}
private async Task HandleValidSubmit()
{
if (_myDto != default)
{
using var scope = _scopeFactory.CreateScope();
var myEntityService = scope.ServiceProvider.GetRequiredService<IMyEntityService>();
_myDto.Id = await myEntityService.Save(_myDto);
}
}
}