ASP.Net核心应用程序。当页面首次打开时,它将从数据库中提取维修订单列表,并将其显示在页面上。效果很好。
数据表的一列带有“编辑”按钮。当您编辑数据时,会弹出一个模态并显示RO信息供他人编辑。
我的问题是,当我单击“保存”时,出现以下错误。
错误代码:
System.InvalidOperationException:'对此进行了第二次操作 上一个操作完成之前的上下文。这通常是由于 通过不同的线程使用相同的DbContext实例,但是 实例成员不保证是线程安全的。这也可以 是由客户端上评估的嵌套查询引起的,如果是 这种情况下重写查询以避免嵌套调用。'
值得注意的是,此功能非常有效,我不知道为使其失败而进行了哪些更改。 OnInitAsync()方法:
protected override async Task OnInitAsync()
{
await LoadData();
}
加载所有数据的方法:
protected async Task LoadData()
{
_repairOrders = await Task.Run(() => RepairOrderService.GetAllRepairOrders().ToArray());
_vehicleLocations = await Task.Run(() => VehicleLocationService.GetAllLocations().ToArray());
_repaitStages = await Task.Run(() => RepairStageService.GetAllRepairStages().ToArray());
_employees = await Task.Run(() => EmployeeService.GetAllEmployees().ToArray());
}
调用保存数据的方法。
protected async Task SaveRepairOrder()
{
if (ro.Id != 0)
{
await Task.Run(() =>
{
RepairOrderService.EditRepairOrder(ro);
});
}
else
{
await Task.Run(() =>
{
RepairOrderService.CreateRepairOrder(ro);
});
}
this.isAdd = false;
await LoadData();
}
这是我的数据访问层类中抛出错误的方法。
//Get all repair order details as a list
public List<RepairOrder> GetAllRepairOrders()
{
try
{
return db.RepairOrder.ToList();
}
catch
{
throw;
}
}
问题开始后,我使LoadData()方法异步。谁能看到我想念的东西?
****更新******** 我更改了“ services.AddSingleton();”到“ services.AddTransient();”它似乎正在工作。我需要深入研究,并尝试确定这是否是我一开始应该做的方式!
答案 0 :(得分:1)
该错误提到这里发生的事情是当您的方法尝试使用该上下文时,其他线程已经在使用该上下文。
将注册更改为Transient可行,因为该实例不再共享...每次获取它,它将是一个新实例...这有一些缺点,例如,如果您尝试“加入”两个“ IQueriable的它将失败...内存消耗也应该增加...
现在,对于您的问题,您必须调试代码流,因为很明显,当您的代码输入LoadData方法时,先前启动的操作正在后台运行。因此,“ Task.Run”实际上与错误无关...如果将其删除,则应该得到相同的错误...
您可以用来验证此假设的方法是在加载数据之前添加了大约一两秒钟的延迟(等待Task.Delay(2000);)……这应该足以允许其他操作完成并释放方法的上下文
答案 1 :(得分:-1)
这里有多个问题。
首先,使用异步。您在各处await Task.Run()
都在做,这仅是有用的。在没有Task.Run()的情况下等待异步Db操作(FindAsync,ToListAsync)会更好。
第二,服务的生命周期管理。 Blazor尚未执行AddScoped(某些启动/身份项目除外)。
因此请注意,当您以常规方式(例如,在MVC应用程序中)注入db
时,它不会被释放。每个客户都有自己的DbContext。在他们的会议期间。
这不一定是问题,但是您应该格外小心,以免发生变更跟踪。也许自己管理Db上下文。
我会写:
public async Task<List<RepairOrder>> GetAllRepairOrders()
{
return await db.RepairOrder
.AsNoTracking() // tracking is expensive, only do it whne needed
.ToListAsync(); // or maybe ToArrayAsync
}
然后在LoadData()
_repairOrders = await RepairOrderService.GetAllRepairOrders();
答案 2 :(得分:-2)
这是Blazor服务器端,对吗?
在服务器端Blazor中,所有添加到DI容器的对象的作用域应为“ 作用域”。对象的生存期跨度到连接的生存期。当您将一个对象添加为Singleton时,其生存期就是应用程序的持续时间。这就是为什么不应将任何对象都添加为Singleton的原因。
我建议您不要使用Task.Run。您的应用程序非常基础,如果您遵循上述步骤,它将可以正常工作。
我建议您使用列表而不是数组来保存数据。