【发布时间】:2021-11-06 12:12:10
【问题描述】:
我有一个看起来像这样的 Blazor 页面。
@inject IMyService MyService
<input value=@myValue @onchange="DoSomethingOnValueChanged">
@code
{
private string myValue;
private async Task DoSomethingOnValueChanged()
{
var myValue = await this.MyService.GetData(this.myValue);
if (myValue != null)
{
myValue.SomeField = "some new value";
await this.MyService.SaveChanges();
}
}
}
服务类如下所示:
public class MyService : IMyService
{
private MyContext context;
public MyService(MyContext context)
{
this.context = context;
}
public async Task<MyObject> GetData(string id)
{
return await this.context.MyDataObjects.FirstOrDefaultAsync(p => p.Id == id);
}
public async Task SaveChanges()
{
await this.context.SaveChangesAsync();
}
}
当用户更改文本框中的值时,我使用我的服务类来获取一些数据,对其进行更新,然后使用实体框架上下文将其保存到数据库中。数据库操作很快(最多需要几秒钟),但用户可以在第一个值完成处理之前输入第二个值,然后开始第二次调用 GetData 和 @987654324 @ 第一个仍在处理中。
其中一个问题是这会导致异常A second operation was started on this context before a previous operation completed. This is usually caused by different threads concurrently using the same instance of DbContext.
我可以通过将上下文工厂注入MyService 构造函数并在每次发出请求时创建一个新上下文来解决这个问题,就像这样
public class MyService
{
private MyContext context;
private IDbContextFactory<MyContext> contextFactory;
public MyService(MyContext contextFactory)
{
this.contextFactory = contextFactory;
}
public async Task MyObject GetData(string id)
{
this.context?.Dispose();
this.context = this.contextFactory.CreateDbContext();
return await this.context.MyDataObjects.FirstOrDefaultAsync(p => p.Id == id);
}
public async Task SaveChanges()
{
if (this.context != null)
{
await this.context.SaveChangesAsync();
}
}
}
但是现在的问题是,如果对 GetData 的第二次调用发生在对 SaveChanges 的第一次调用发生之前,则原始数据所附加到的上下文已被释放,因此 SaveChanges 在第一个值上会失败。
另一个问题是,在实际程序中,MyService 有几个注入的子服务依赖项,它们也使用实体框架上下文。 MyService 的这么多方法会导致其子服务也实例化新的实体框架上下文,这会导致同样的问题,即某些数据仍在处理中,但其拥有的上下文已被释放。
为了解决这个问题,我决定每次调用页面的DoSomethingOnValueChanged 方法时都实例化一个新服务。该代码如下所示:
public interface ITypeFactory<T>
{
Func<T> CreateFunction { get; set; }
T Create();
}
public class TypeFactory<T> : ITypeFactory<T>
{
public Func<T> CreateFunction { get; set; }
public T Create()
{
return CreateFunction();
}
}
Startup.cs
public void ConfigureServices(IServiceCollection services)
{
services.AddTransient(f =>
{
ITypeFactory<IMyService> factory = ActivatorUtilities.CreateInstance<TypeFactory<IMyService>>(f);
factory.CreateFunction = () => ActivatorUtilities.CreateInstance<MyService>(f);
return factory;
}
}
然后我将该工厂注入我的 Razor 页面并在每次调用 DoSomethingOnValueChanged 时创建一个新的 MyService 实例。
这个系统有效,它避免了同一个上下文被同时使用两次的问题,但我担心如果服务开始有很多依赖项,那么频繁地创建一个新服务会导致明显的性能损失注入其中,或者上下文中的模型配置量变得非常大。
- 现在,创建服务实例似乎需要大约 0.05 秒。对于具有复杂依赖关系图的服务或大量 EF 上下文配置,这会大大降低性能吗?
- 这种服务工厂系统是一种合理的处理方式吗?当我搜索这个时,我只能找到有关使用
DbContext工厂的信息,并且我还没有找到任何讨论使用工厂来提供使用DbContexts 的服务的信息 - 有没有更好的方法来处理这个问题?
【问题讨论】:
标签: c# dependency-injection entity-framework-core blazor blazor-server-side