0

我遇到了 .NET(框架,带 WinForm 和 WebForms)/MS SQL 项目,其中数据库中的重要表包含一个 TIMESTAMP(又名 ROWVERSION)列(称为 tsModified)以防止并发问题。此外,该项目不允许应用层直接与表交互(相反,所有 CRUD 和业务逻辑都必须通过存储过程完成)。

让我发疯的一件事是如何在 UPDATE 期间使用可以解释 TIMESTAMP 列的 SqlDataSource。

CRUD procs 的基本形式如下:

CREATE PROC Customer_List
  @pk_Customer INT = null
SELECT id, name, tsModified 
FROM Customer 
WHERE @pk_Customer IS NULL OR @pk_Customer = id;

CREATE PROC Customer_Update
  @id INT,
  @name varchar,
  @tsMod TIMESTAMP
IF NOT EXISTS (SELECT TOP 1 1 FROM Customer where id=@id and tsModified=@tsModified)
    Return; --actually RAISEERROR w/ a concurrency alert telling the user to refresh & try again

UPDATE Customer SET [param/value pairs] WHERE id = @id;

当然,您可以手动定义部分类和方法来解释 tsModified,然后使用 asp:ObjectDataSource,但这是额外的工作。我只想方便地在表单上放置一个 asp:SqlDataSource,然后继续我的一天。

但是... SqlDataSource 不喜欢将 TIMESTAMP 作为参数。事实上,我确实花了几天时间研究如何使这项工作发挥作用,并遇到很多其他人遇到同样的问题。

我终于弄明白了。请参阅下面的答案。

4

1 回答 1

0

以下是在使用存储过程时如何使用带有 asp:SqlDataSource 的 MS SQL ROWVERSION (TIMESTAMP) 列来处理并发。

像这样设置你的 SqlDataSource:

<asp:SqlDataSource ID="dsRegs" runat="server" OnUpdating="dsRegs_Updating" ConnectionString="[your connstr]" InsertCommand="RegulatoryAgency_Insert" InsertCommandType="StoredProcedure" SelectCommand="RegulatoryAgency_List" SelectCommandType="StoredProcedure" UpdateCommand="RegulatoryAgency_Update" UpdateCommandType="StoredProcedure">
            <InsertParameters>
                <asp:Parameter Name="RegulatoryCode" Type="String" />
                <asp:Parameter Name="RegulatoryName" Type="String" />
                <asp:Parameter Name="RegulatoryState" Type="String" />
            </InsertParameters>
            <SelectParameters>
                <asp:Parameter Name="pk_RegulatoryAgency" Type="DBNull" />
            </SelectParameters>
            <UpdateParameters>
                <asp:Parameter Name="pk_RegulatoryAgency" Type="Int32" />
                <asp:Parameter Name="RegulatoryCode" Type="String" />
                <asp:Parameter Name="RegulatoryName" Type="String" />
                <asp:Parameter Name="RegulatoryState" Type="String" />
                <asp:Parameter Direction="InputOutput" Name="tsModified" Type="Empty" />
            </UpdateParameters>
        </asp:SqlDataSource>

需要注意的重要事项是:

  1. 在 UpdateParameters 中,tsModified 是 TIMESTAMP 值和 Type="Empty"。
  2. OnUpdating 设置为 dsRegs_Updating 事件。

现在后面的代码:

    /// <summary>
    /// When editing for this record/row begins in the grid, we need to get the primary key from the row, 
    /// and then stuff the TIMESTAMP (tsModified) into a Session variable so it persists
    /// </summary>
    protected void gvRegs_StartRowEditing(object sender, DevExpress.Web.Data.ASPxStartRowEditingEventArgs e)
    {
        int pk = (int)e.EditingKeyValue;
        var db = new myDataContext();
        var ra = db.RegulatoryAgency_List(pk).First();
        Session["tsModified"] = ra.tsModified;
    }

    /// <summary>
    /// Before we call the database, convert the Session var back the original Linq-to-SQL type (System.Data.Linq.Binary), then
    /// convert it to a (byte) array, and update the SqlDataSource parameter with the correct value.
    /// </summary>
    protected void dsRegs_Updating(object sender, SqlDataSourceCommandEventArgs e)
    {
        DbParameter dp = e.Command.Parameters["@tsModified"];
        dp.Value = ((System.Data.Linq.Binary)Session["tsModified"]).ToArray();
    }

在这个例子中,前面使用的是 DevExpress ASPxGridView,但是数据绑定和事件在其他数据绑定控件上应该是类似的。当行编辑开始时,我们从数据库中提取记录的 tsModified 值并将其放入 Session 变量中。然后 SqlDataSource 触发它的 Updating 事件,我们获取 Session 变量,将其转换回原来的格式(在我的例子中是 System.Data.Linq.Binary,因为这个例子使用的是 Linq-to-SQL),最后是最后一个技巧是您不能将 TIMESTAMP 值作为二进制、varbinary 或字节传递 - 必须作为 btye[] 发送,.ToArray() 正在处理。

使用这样的代码,我可以通过 SqlDataSource 成功地 SELECT、INSERT、UPDATE 和 DELETE,并且数据库中的 tsModified (TIMESTAMP) 值按预期递增。

于 2021-09-03T18:36:55.793 回答