简体   繁体   English

System.InvalidOperationException:将数据保存到我的数据库时

[英]System.InvalidOperationException: when saving data to my database

ASP.Net core blazor application. ASP.Net 核心 blazor 应用程序。 When the page first opens it pulls a list of repair orders from the database and displays them on the page.当页面首次打开时,它会从数据库中提取维修订单列表并将它们显示在页面上。 This works great.这很好用。

The table of data has a column with an 'Edit' button.数据表有一列带有“编辑”按钮。 When you edit the data a modal pops up and displays the RO information for someone to edit.当您编辑数据时,会弹出一个模式并显示 RO 信息供其他人编辑。

My problem is when I click 'Save', I get the following error.我的问题是当我单击“保存”时,出现以下错误。

Error Code:错误代码:

System.InvalidOperationException: 'A second operation started on this context before a previous operation completed. System.InvalidOperationException: '在上一个操作完成之前,第二个操作在此上下文中启动。 This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe.这通常是由不同的线程使用相同的 DbContext 实例引起的,但是不保证实例成员是线程安全的。 This could also be caused by a nested query being evaluated on the client, if this is the case rewrite the query avoiding nested invocations.'这也可能是由在客户端上评估嵌套查询引起的,如果是这种情况,请重写查询以避免嵌套调用。

It is worth noting this feature worked great and I have no clue what I changed to make it fail.值得注意的是这个功能效果很好,我不知道我做了什么让它失败。 OnInitAsync() method: OnInitAsync() 方法:

protected override async Task OnInitAsync()
    {
        await LoadData();        
    }

Method to load all the data:加载所有数据的方法:

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());
    }

Method called to save data.为保存数据而调用的方法。

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();
    }

This is the method in my Data access layer class that is throwing the error.这是我的数据访问层 class 中抛出错误的方法。

//Get all repair order details as a list
        public List<RepairOrder> GetAllRepairOrders()
        {
            try
            {
                return db.RepairOrder.ToList();
            }
            catch
            {
                throw;               
            }
        }

I made the LoadData() method async after the issue started.问题开始后,我使 LoadData() 方法异步。 Can anyone see what I am missing?谁能看到我错过了什么?

**** Update ******** I changed "services.AddSingleton();" **** 更新 ******** 我更改了“services.AddSingleton();” to "services.AddTransient();"到“services.AddTransient();” and it seems to be working.它似乎正在工作。 I need to dig in and try to determine if this was the way I should have done this in the beginning!我需要深入研究并尝试确定这是否是我一开始就应该这样做的方式!

As the error mentions what is happening here is that some other thread is already using the context as your method is trying to use it. 正如错误所提到的,这里正在发生的事情是,当您的方法尝试使用该上下文时,其他线程已经在使用该上下文。

Changing the registration to Transient works because the instance is no longer shared... each time you get it, it will be new one... this has some drawbacks, for instance, if you try to "join" two "IQueriable"s it will fail... memory consumption should also go UP... 将注册更改为Transient是可行的,因为实例不再共享了……每次获取它,它将是一个新实例……这有一些缺点,例如,如果您尝试“加入”两个“ IQueriable”它将失败...内存消耗也应该上升...

Now, to your problem, you have to debug the code flow, because apparently, a previously started operation is running on the background AS YOUR CODE ENTERS the LoadData method! 现在,对于您的问题,您必须调试代码流,因为显然,当您的代码输入LoadData方法时,先前启动的操作正在后台运行。 So the "Task.Run" doesnt actually have nothing to do with the error... if you remove it you should get the same error... 因此,“ Task.Run”实际上与错误无关...如果删除它,您应该得到相同的错误...

Something you can do to verify this hypothesis is adding a delay (await Task.Delay(2000);) of about a second or two before loading data... this should be enough to allow the other operation to finish, releasing the context for your method 您可以用来验证此假设的操作是在加载数据之前添加了大约一秒或两秒的延迟(等待Task.Delay(2000);)...这应该足以允许其他操作完成,从而释放你的方法

There is more than one issue here. 这里有多个问题。

First, the use of async. 首先,使用异步。 You are doing await Task.Run() everywhere, that is only limited useful. 您到处都在await Task.Run() ,这仅是有用的。 Much better to await an async Db operation (FindAsync, ToListAsync) without Task.Run(). 没有Task.Run()更好地等待异步Db操作(FindAsync,ToListAsync)。

Second, the lifetime management of Services. 第二,服务的生命周期管理。 Blazor doesn't do AddScoped yet (except maybe for some startup/identity items). Blazor尚未执行AddScoped(某些启动/身份项目除外)。

So be aware that when you inject that db the normal way (eg like in a MVC application) it will not get Disposed. 因此请注意,当您以正常方式(例如,在MVC应用程序中)注入该db时,它不会被释放。 Each of your clients has its own DbContext. 每个客户都有自己的DbContext。 For the length of their session. 在他们的会议期间。

Which does not have to be a problem but you should pay extra care in avoiding Changetracking. 这不一定是问题,但是您应该格外小心,避免使用Changetracking。 And maybe manage the Db context yourself. 也许自己管理Db上下文。

I would write: 我会写:

public async Task<List<RepairOrder>> GetAllRepairOrders()
{
     return await db.RepairOrder
       .AsNoTracking()   // tracking is expensive, only do it whne needed
       .ToListAsync();   // or maybe ToArrayAsync
}

and then, in LoadData() 然后在LoadData()中

_repairOrders = await RepairOrderService.GetAllRepairOrders();

This is a Blazor server-side, right ? 这是Blazor服务器端,对吗?

In server-side Blazor all the objects added to the DI container should be scoped " Scoped ". 在服务器端Blazor中,添加到DI容器的所有对象的作用域应为“ 作用域 ”。 The life-time of an object is spanned to that of the connection. 对象的生存期跨度到连接的生存期。 When you add an object as a Singleton, its life-time is the duration of the application. 当您将一个对象添加为Singleton时,其生存期就是应用程序的持续时间。 Which is why none of your objects should be added as Singleton. 这就是为什么都不要将任何对象添加为Singleton的原因。

I'd advice you against using Task.Run. 我建议您不要使用Task.Run。 Your app is very basic, and it should work if you follow the above. 您的应用程序非常基础,如果您遵循上述步骤,它将可以正常工作。

I'd advise you to use Lists instead of Arrays to hold your data. 我建议您使用列表而不是数组来保存数据。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 System.InvalidOperationException 保存 - System.InvalidOperationException on saving 尝试保存在数据库中时 System.InvalidOperationException - System.InvalidOperationException when trying to save in database 无法连接到数据库,获取System.InvalidOperationException - Cannot connect to database, getting System.InvalidOperationException 调用WCF服务时出现System.InvalidOperationException - System.InvalidOperationException when calling WCF Service 填充数组时出现System.InvalidOperationException错误 - System.InvalidOperationException error when populating an array System.InvalidOperationException:提交消息时 - System.InvalidOperationException: When submitting a message System.InvalidOperationException:访问DbContext时 - System.InvalidOperationException: when accessing DbContext 管理对话框时出现System.InvalidOperationException - System.InvalidOperationException when managing a dialog 使用ApiController获取列表时出现System.InvalidOperationException - System.InvalidOperationException when getting a List with an ApiController 在断开连接的情况下保存实体图时,EF Core 3.1 抛出 System.InvalidOperationException - EF Core 3.1 throw System.InvalidOperationException when saving entity graph in Disconnected scenario
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM