【问题标题】:WCF and SQL Server - how to handle data change?WCF 和 SQL Server - 如何处理数据更改?
【发布时间】:2009-07-19 06:48:08
【问题描述】:

我有一个应用程序,其架构类似于 client/server/db。客户端和服务器之间的通信是WCF(从asmx迁移过来的),数据库是SQL Server 2005。

该项目有一个要求,即您无法更新订单,该订单在您初次阅读后已被其他用户更改(更新)。我认为大多数应用程序中的一个常见要求。

一个订单的更新通常是这样的:

  1. 客户端读取订单 - 初始读取副本存储(会话)在服务器上
  2. 客户端更新订单 - 返回 更新订单 到服务器
  3. 服务器将再次从数据库中读取订单并与初始读取进行比较以检查订单是否已被其他用户更改 - 以防客户端将被通知重新读取订单
  4. 服务器将保存更改

这种处理数据更改的方法的效果是,在某个点 (3),服务器将在内存中拥有 3 个(不同的)订单副本!任何人都知道另一种策略吗?

我们正在运行带有 AspNetBackwardCompability 的 WCF,因为我们需要 Session 变量来“保存”初始读取副本 - 如果我们可以转储它,它会让我很开心

【问题讨论】:

    标签: sql-server wcf


    【解决方案1】:

    一种解决方案是让客户端在保存时提供初始读取的值和更新的值。那么你就不需要会话中原始值的副本了。

    DataSet 具有存储两个版本(DataRowVersion.Original 和 DataRowVersion.Current)的内置功能,但您必须提供自己的方法来执行此操作(例如 operationContract:

    SaveMyData(MyType original, MyType updated);
    

    然后您可以这样保存到数据库中:

    UPDATE MyTable
    SET Col1 = @NewCol1, Col2 = @NewCol2, ...
    WHERE Col1 = @OldCol1, Col2 = @OldCol2, ...
    IF @@ROWCOUNT = 0 ... update failed ...
    

    或者,您可以在表中包含一个 TIMESTAMP / ROWVERSION 列。您将其往返于客户端,并在更新时对其进行测试:

    UPDATE MyTable
    SET Col1 = @NewCol1, Col2 = @NewCol2, ...
    WHERE PKCol = @PK AND TimeStampCol = @OldTimeStamp
    IF @@ROWCOUNT = 0 ... update failed ...
    

    您当然依赖客户端在保存时正确返回原始值/原始时间戳。但这不是一个安全问题 - 恶意客户端所造成的破坏不会比您使用基于会话的解决方案造成的破坏更大。

    【讨论】:

    • 嗨乔,我认为这是一个很好的方法,但是发送两个版本(原始版本和当前版本)都会提供额外的数据,这些数据必须再次发送和发送回服务器 - 我们正在努力解决相当多的问题数据。我更喜欢你的时间戳版本。
    【解决方案2】:

    如何防止在 3 之后、4 之前发生并发更改?

    处理这个问题的通常方法是消除第 3 步(无论如何都是不正确的,除非在可重复的读取隔离级别中完成,这完全是多余的)并乐观地应用更改,因为没有任何改变(即乐观并发模型)。要强制执行确实没有任何更改,您可以使用包含 all 旧值的 WHERE 子句,或者添加在 每次 更新时更改的 WHERE 子句特殊列,例如时间戳或行版本。

    如果更新是空操作(它没有找到旧值,使用各种方式检查,例如检查 @@ROWCOUNT 或使用 OUTPUT 子句),那么您可以阅读新建、修改、取值并通知客户端,仅在异常情况下。

    【讨论】:

      【解决方案3】:

      您已实现的有状态服务是一种大型服务反模式。作为一般原则,Web 服务应该是无状态的,否则您的可伸缩性可能会受到影响。对于乐观锁定,使用表的时间戳列。将此作为并发令牌返回给客户端,该令牌保持不变,并与更新前数据库中的值进行比较。我对 sql server 很陌生,但 oracle 有 select for update 语句可以帮助你。

      如果数据分布在许多表中,请考虑使用存储过程的合适锁定策略。

      【讨论】:

        【解决方案4】:

        我喜欢 Joe 的方法 - 这也是我非常推荐的方法。

        在您的初始读取中,将主表中的 TIMESTAMP 列的值发送回客户端,可以在实际 DataContract 中,也可以作为 WCF 响应消息中的标头。

        当您想要更新数据时,将初始时间戳值从客户端发送回服务器。然后,服务器将首先检查该时间戳值是否已更改,如果是,则抛出 FaultException 并且不更新数据。只有当时间戳值仍然与客户端在 UPDATE 调用中返回的值相同时,服务器才会真正执行更新。

        我建议使用 SQL Server TIMESTAMP 数据时间(顺便说一句,这实际上与日期和/或时间无关 - 它只是一个唯一的、不断增加的数字,真的),因为这更多比 DATETIME 准确,并且每次写入表中的行时,SQL Server 都会自动更新它。是检查更新的完美“标记”。

        使用这种方法,您只需传递一个 8 字节的时间戳值 - 无需拥有整个数据行的三个精确副本。

        请参阅这篇精彩的文章“Understanding TIMESTAMP (ROWVERSION) in SQL Server”。

        马克

        【讨论】:

          猜你喜欢
          • 2022-01-07
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-02-12
          • 1970-01-01
          • 2014-05-25
          • 2010-09-24
          • 2011-06-26
          相关资源
          最近更新 更多