使用存储过程的实体框架并发问题

时间:2022-12-23 02:08:47

I am using ASP.NET to build a application for a retail company. I am using the Entity Framework (model-first) as my data access layer. I am using stored procedures to do my CRUD operations and all columns are mapped and seems to be correct as all CRUD functionality are working as expected.

我用ASP。为零售公司构建应用程序。我使用实体框架(模型优先)作为我的数据访问层。我正在使用存储过程来执行CRUD操作,并且所有的列都被映射,并且看起来是正确的,因为所有CRUD功能都是按预期工作的。

But I am having concurrency issues with the DELETE operation.

但是我的删除操作存在并发问题。

I've added a TimeStamp column to the table I am doing the CRUD operation on. The UPDATE operation works fine as it is updating by primary key and the TimeStamp value. Thus if no rows are affected with the UPDATE operation, because of a change in the TimeStamp value, the Entity Framework throws a OptimisticConcurrencyException.

我已经向正在执行CRUD操作的表添加了一个时间戳列。更新操作可以正常工作,因为它通过主键和时间戳值进行更新。因此,如果更新操作不影响任何行,由于时间戳值的更改,实体框架将抛出OptimisticConcurrencyException。

The DELETE operation works on the same principle as it is deleting by primary key and the TimeStamp value. But no exception is thrown when the TimeStamp value does not match between the entity and the database.

删除操作的工作原理与使用主键和时间戳值删除操作相同。但是,当实体和数据库之间的时间戳值不匹配时,不会抛出异常。

In the C# delete method I do retrieve the latest record first and then update the TimeStamp property to another TimeStamp value (It might be different to the retrieved value). After some investigation by using SQL Profiler I can see that the DELETE stored procedure is executed but the TimeStamp parameter that is passed to the stored procedure is the latest TimeStamp value and not the value that I have set the TimeStamp property to. Thus the record is deleted and the Entity Framework does not throw an exception.

在c# delete方法中,我首先检索最新的记录,然后将时间戳属性更新为另一个时间戳值(它可能与检索到的值不同)。在使用SQL Profiler进行了一些调查之后,我可以看到执行了DELETE存储过程,但是传递给存储过程的时间戳参数是最新的时间戳值,而不是我将时间戳属性设置为的值。因此,记录被删除,实体框架不会抛出异常。

Why would the Entity Framework still pass the retrieved TimeStamp value to the Stored Procedure and not the value that I have assigned the property? Is this be design or am I missing something?

为什么实体框架仍然将检索到的时间戳值传递给存储过程,而不传递给我分配属性的值?这是设计还是我漏掉了什么?

Any help will be appreciated! (where is Julie Lerman when you need her! :-))

如有任何帮助,我们将不胜感激!(当你需要朱莉·勒曼的时候,她在哪里?):-))

2 个解决方案

#1


2  

Optimistic concurrency in EF works fine. Even with stored procedures.

乐观并发在EF中运行良好。即使有存储过程。

ObjectContext.DeleteObjects passes original values of entity to delete function. This makes sense. Original values are used to identify the row to delete. When you delete object, you don't (usually) have meaningful edits to your entity. What do you expect EF to do with then? Write? To what records?

ObjectContext。DeleteObjects将实体的原始值传递给delete函数。这是有意义的。原始值用于标识要删除的行。删除对象时,通常不会对实体进行有意义的编辑。那你对英孚有什么期望呢?写什么?记录什么?

One legitimate use for passing modified data to delete function is when you want to track deletes in some other table and you need to throw in some information not accessible at database layer, only at business layer. Examples include application level user name or reason to delete. In this situation you need to construct entity with this values as original values. One way to do it:

传递修改后的数据以删除函数的一个合法用途是,当您希望跟踪其他表中的删除时,您需要抛出一些在数据库层(仅在业务层)无法访问的信息。示例包括应用程序级用户名或删除理由。在这种情况下,您需要用这些值作为原始值来构造实体。一种方法是:

var x = db.MyTable.Single(k => k.Id == id_to_delete);
x.UserName = logged_in_user;
x.ReasonForChange = some_reason;
// [...]
db.ObjectStateManager.ChangeObjectState(x, EntityState.Unchanged);
db.MyTable.DeleteObject(x);
db.SaveChanges();

Of course, better strategy might be to do it openly in business layer.

当然,更好的策略可能是在业务层中公开这样做。


I don't understand your use case with rowversion/timestamp.

我不理解您使用行版本/时间戳的用例。

  1. To avoid concurrency issues you pass original timestamp to modifying code. That way it can be compared to current value in database to detect if record changed since you last read it. Comparing it with new value makes little sense.

    为了避免并发问题,您将原始时间戳传递给修改代码。这样就可以将它与数据库中的当前值进行比较,以检测自上次读取记录以来记录是否发生了更改。将它与新的价值进行比较没有什么意义。

  2. You usually use change markers that are automatically updated by database like rowversion/timestamp in SQL Server, rowversion in Oracle or xmin in PostgreSQL. You don't change its value in your code.

    通常使用数据库自动更新的更改标记,如SQL Server中的rowversion/timestamp、Oracle中的rowversion或PostgreSQL中的xmin。在代码中不会改变它的值。

  3. Still, if you maintain row version manually, you need to provide:

    不过,如果您手动维护行版本,您需要提供:

    a) new version to insert and update to be written, and

    a)插入和更新要写入的新版本,以及

    b) old version (read from database) to update and delete to check for concurrent changes.

    b)旧版本(从数据库读取)更新和删除,以检查并发更改。

    You don't send new value to delete. You don't need to. Also, when using stored procedures for modification, it's better to compute new version in the procedure and return it to application, not the other way around.

    不发送要删除的新值。你不需要。此外,在使用存储过程进行修改时,最好在过程中计算新版本并将其返回给应用程序,而不是反过来。

#2


0  

Hard to tell without seeing any code, but maybe when the postback occurs the page is being re-bound before your delete method is firing? On whatever method databinds the form controls (I assume it's OnLoad or OnInit), have you wrapped any databinding calls with if ( !this.IsPostBack ) { ... }?

在没有看到任何代码的情况下很难判断,但也许当回发发生时,在删除方法触发之前,页面正在被重新绑定?在窗体控件的任何方法databinds(我假设它是OnLoad或OnInit)上,都要使用if (!this)包装任何数据库调用。IsPostBack){…} ?

Also I'm not sure if there's a reason why you're explicitly storing the concurrency flag in viewstate/session variables, but a better way to do it (imo) is to add the timestamp to the DataKeyNames property of the FormView/GridView (ie: <asp:FormView ID='blah' runat='server' DataKeyNames='Id, Timestamp'>.

我也不确定为什么要在viewstate/session变量中显式地存储并发标志,但是更好的方法(imo)是将时间戳添加到FormView/GridView的DataKeyNames属性(即:

This way you don't have to worry about manually storing or updating the timestamp. ;)

这样,您就不必担心手工存储或更新时间戳。,)

#1


2  

Optimistic concurrency in EF works fine. Even with stored procedures.

乐观并发在EF中运行良好。即使有存储过程。

ObjectContext.DeleteObjects passes original values of entity to delete function. This makes sense. Original values are used to identify the row to delete. When you delete object, you don't (usually) have meaningful edits to your entity. What do you expect EF to do with then? Write? To what records?

ObjectContext。DeleteObjects将实体的原始值传递给delete函数。这是有意义的。原始值用于标识要删除的行。删除对象时,通常不会对实体进行有意义的编辑。那你对英孚有什么期望呢?写什么?记录什么?

One legitimate use for passing modified data to delete function is when you want to track deletes in some other table and you need to throw in some information not accessible at database layer, only at business layer. Examples include application level user name or reason to delete. In this situation you need to construct entity with this values as original values. One way to do it:

传递修改后的数据以删除函数的一个合法用途是,当您希望跟踪其他表中的删除时,您需要抛出一些在数据库层(仅在业务层)无法访问的信息。示例包括应用程序级用户名或删除理由。在这种情况下,您需要用这些值作为原始值来构造实体。一种方法是:

var x = db.MyTable.Single(k => k.Id == id_to_delete);
x.UserName = logged_in_user;
x.ReasonForChange = some_reason;
// [...]
db.ObjectStateManager.ChangeObjectState(x, EntityState.Unchanged);
db.MyTable.DeleteObject(x);
db.SaveChanges();

Of course, better strategy might be to do it openly in business layer.

当然,更好的策略可能是在业务层中公开这样做。


I don't understand your use case with rowversion/timestamp.

我不理解您使用行版本/时间戳的用例。

  1. To avoid concurrency issues you pass original timestamp to modifying code. That way it can be compared to current value in database to detect if record changed since you last read it. Comparing it with new value makes little sense.

    为了避免并发问题,您将原始时间戳传递给修改代码。这样就可以将它与数据库中的当前值进行比较,以检测自上次读取记录以来记录是否发生了更改。将它与新的价值进行比较没有什么意义。

  2. You usually use change markers that are automatically updated by database like rowversion/timestamp in SQL Server, rowversion in Oracle or xmin in PostgreSQL. You don't change its value in your code.

    通常使用数据库自动更新的更改标记,如SQL Server中的rowversion/timestamp、Oracle中的rowversion或PostgreSQL中的xmin。在代码中不会改变它的值。

  3. Still, if you maintain row version manually, you need to provide:

    不过,如果您手动维护行版本,您需要提供:

    a) new version to insert and update to be written, and

    a)插入和更新要写入的新版本,以及

    b) old version (read from database) to update and delete to check for concurrent changes.

    b)旧版本(从数据库读取)更新和删除,以检查并发更改。

    You don't send new value to delete. You don't need to. Also, when using stored procedures for modification, it's better to compute new version in the procedure and return it to application, not the other way around.

    不发送要删除的新值。你不需要。此外,在使用存储过程进行修改时,最好在过程中计算新版本并将其返回给应用程序,而不是反过来。

#2


0  

Hard to tell without seeing any code, but maybe when the postback occurs the page is being re-bound before your delete method is firing? On whatever method databinds the form controls (I assume it's OnLoad or OnInit), have you wrapped any databinding calls with if ( !this.IsPostBack ) { ... }?

在没有看到任何代码的情况下很难判断,但也许当回发发生时,在删除方法触发之前,页面正在被重新绑定?在窗体控件的任何方法databinds(我假设它是OnLoad或OnInit)上,都要使用if (!this)包装任何数据库调用。IsPostBack){…} ?

Also I'm not sure if there's a reason why you're explicitly storing the concurrency flag in viewstate/session variables, but a better way to do it (imo) is to add the timestamp to the DataKeyNames property of the FormView/GridView (ie: <asp:FormView ID='blah' runat='server' DataKeyNames='Id, Timestamp'>.

我也不确定为什么要在viewstate/session变量中显式地存储并发标志,但是更好的方法(imo)是将时间戳添加到FormView/GridView的DataKeyNames属性(即:

This way you don't have to worry about manually storing or updating the timestamp. ;)

这样,您就不必担心手工存储或更新时间戳。,)