c# - 我应该像这样将服务工厂注入我的 Blazor 页面吗?
问题描述
我有一个看起来像这样的 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
,SaveChanges
而第一个值是还在处理。
问题之一是这会导致异常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();
}
}
启动.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 页面并在MyService
每次DoSomethingOnValueChanged
调用时创建一个新实例。
这个系统可以工作,它避免了同一个上下文同时使用两次的问题,但我担心如果服务开始注入大量依赖项,那么频繁地创建一个新服务会导致明显的性能损失,或者上下文中的模型配置量变得非常大。
- 现在,创建一个服务实例似乎需要大约 0.05 秒。对于具有复杂依赖关系图的服务或大量 EF 上下文配置,这会大大降低性能吗?
- 这种服务工厂系统是一种合理的处理方式吗?当我搜索这个时,我只能找到有关使用
DbContext
工厂的信息,并且我没有找到任何讨论使用工厂来使用DbContext
s的服务的信息 - 有没有更好的方法来处理这个?
解决方案
像这样实例化服务不是很干净。您可以执行以下操作:
在每个方法中使用 dbcontext factory。请注意,上下文现在限定为方法。'using' 关键字在方法退出后立即处理上下文,因此您不必手动检查。
public async Task<MyObject> GetData(string id) { using var ctx = this.contextFactory.CreateDbContext(); return await ctx.MyDataObjects.FirstOrDefaultAsync(p => p.Id == id); }
将实体传递给更新方法。使用 dbcontext Update() 更新并保存更新的实体
public async Task SaveChanges(MyObject obj) { using var ctx = this.contextFactory.CreateDbContext(); ctx.Update(obj); await ctx.SaveChangesAsync(); }
这应该可以解决您的问题。您不需要手动实例化服务并解决随之而来的复杂性。需要记住的几件事:Blazor 在断开连接的场景中大部分都运行良好。因此,您将负责管理状态以及由此产生的并发问题。在此处阅读有关断开连接的情况:https ://docs.microsoft.com/en-us/ef/core/saving/disconnected-entities
您可以直接在 blazor 组件中使用 DbContext。在此处阅读 OwningComponentBase:https ://docs.microsoft.com/en-us/aspnet/core/blazor/fundamentals/dependency-injection?view=aspnetcore-5.0&pivots=server#utility-base-component-classes-to -manage-a-di-scope-1
推荐阅读
- java - 如何创建 ArchUnit 规则来验证抛出的所有异常都继承自特定的自定义异常?
- http - 使用 HTTP/2 和后端服务 HTTP/1.1 的反向代理的性能
- javascript - 使用 moment.js 将日期范围对象转换为 ISO-8601 格式
- excel - Excel - 如何根据另一个单元格的颜色更改形状颜色
- html - 如何在Django项目中组织大量页面的分页?
- clearcase - 涉及多个元素的 ClearCase 路径名
- c++ - 在cmake中从源代码构建opencv4.4.0时出错
- flutter - 如何测试来自 admob 的真实广告
- python - TypeErr:无法腌制“_thread.RLock”对象 - 目标:以 .joblib 格式保存模型以便在需要 .joblib 后重用
- javascript - DiscordJS 验证命令