3

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

但是我遇到了 DELETE 操作的并发问题。

我在表中添加了一个TimeStamp列,我正在对其进行 CRUD 操作。UPDATE 操作工作正常,因为它通过主键和TimeStamp值进行更新。因此,如果没有行受到 UPDATE 操作的影响,由于TimeStamp值的变化,实体框架会抛出一个OptimisticConcurrencyException.

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

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

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

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

4

2 回答 2

3

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

ObjectContext.DeleteObjects将实体的原始值传递给删除函数。这是有道理的。原始值用于标识要删除的行。当您删除对象时,您(通常)不会对您的实体进行有意义的编辑。那么你希望 EF 做什么呢?写?到什么记录?

将修改后的数据传递给删除函数的一种合法用途是当您想要跟踪其他表中的删除并且您需要输入一些在数据库层无法访问的信息时,只能在业务层访问。示例包括应用程序级用户名或删除原因。在这种情况下,您需要将此值作为原始值来构造实体。一种方法:

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

当然,更好的策略可能是在业务层公开进行。


我不了解您使用 rowversion/timestamp 的用例。

  1. 为避免并发问题,您将原始时间戳传递给修改代码。这样就可以将它与数据库中的当前值进行比较,以检测自上次读取后记录是否已更改。将其与新值进行比较毫无意义。

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

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

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

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

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

于 2013-02-07T14:28:57.270 回答
0

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

此外,我不确定是否有理由将并发标志显式存储在视图状态/会话变量中,但更好的方法(imo)是将时间戳添加到 FormView/GridView 的 DataKeyNames 属性中(即:<asp:FormView ID='blah' runat='server' DataKeyNames='Id, Timestamp'>

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

于 2012-10-29T12:04:13.930 回答