使用 ADO.NET 自动生成的更新存储过程进行级联更新

发布于 2024-10-01 00:55:05 字数 2274 浏览 1 评论 0原文

我正在运行带有 .NET 2.0 的 MS SQL Server 2005。我当前的应用程序是用 C# 编写的。

在 MSSQL 中,我创建了 2 个测试表来说明我的问题:

表 1 设置为:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[T1](
 [n] [bigint] NOT NULL,
 [t] [varchar](10) NOT NULL,
 CONSTRAINT [PK_T1] PRIMARY KEY CLUSTERED 
(
 [n] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF

表 2 设置为:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[T2](
 [n] [bigint] NULL,
 [Test] [varchar](4) NOT NULL,
 [Num] [bigint] NULL
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[T2]  WITH CHECK ADD  CONSTRAINT [FK_T2_T1] FOREIGN KEY([n])
REFERENCES [dbo].[T1] ([n])
ON UPDATE CASCADE
GO
ALTER TABLE [dbo].[T2] CHECK CONSTRAINT [FK_T2_T1]

我在每个表中填充了多行。

简而言之,表 1 (T1) 有一个 bigint 主键字段“n”,映射到子表 T2 的 n 字段。设置级联更新,因此当修改 T1.n 时,T2.n 也会更新。如果我执行一个简单的查询并在其中一行中设置 T1.n 的值,我可以看到执行计划中发生级联。如果我设置 T1.t 的值,则执行计划中不会按预期发生级联。

在 ADO.NET 中,我在表单中添加了一个 DataAdapter,并让它自动生成 4 个存储过程,T1 的更新显示如下:(我添加了一些空格以帮助提高可读性)

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[T1_Update]
(
 @n bigint,
 @t varchar(10),
 @Original_n bigint,
 @Original_t varchar(10)
)
AS
 SET NOCOUNT OFF;
UPDATE [T1] 
SET 
[n] = @n, 
[t] = @t 
WHERE (
([n] = @Original_n) AND 
([t] = @Original_t));

SELECT n, t FROM T1 WHERE (n = @n)

方式执行此存储过程:

DECLARE @RC int
DECLARE @n bigint
DECLARE @t varchar(10)
DECLARE @Original_n bigint
DECLARE @Original_t varchar(10)

EXECUTE @RC = [T1_Update] 
   5, '5', 5, '4' -- n, t, Original_n, Original_t

我可以使用以下 n 的值不变,t 的值发生变化。我希望看到与我刚刚执行“UPDATE T1 SET t='4' where n=5 and t='5'”相同的行为/执行计划。运行此查询的执行计划非常简单,其中包括 CI 查找、计算标量、CI 更新、更新。

但是,运行上面的执行时,执行计划显示它正在扫描表 T2 并在该表上执行更新,即使该值没有更改。

我认为这是由于以下几行造成的:

UPDATE [T1] 
SET 
[n] = @n, 

注释掉 T1_Update 存储过程中的 [n] = @n,即使该值没有更改,也会阻止级联更新触发。

因此,假设我想保留级联更新,是否有办法重写/修改自动生成的 T1_Update 存储过程,以便级联更新仅在“n”的值更改时激活,而在值不变时不激活?

编辑:我删除了 T1 上引用自身的外键关系,该关系是在我设置此测试时意外添加的。

I'm running MS SQL Server 2005 with .NET 2.0. My current application is written in C#.

In MSSQL I have created 2 test tables to illustrate my question:

Table 1 is setup as:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[T1](
 [n] [bigint] NOT NULL,
 [t] [varchar](10) NOT NULL,
 CONSTRAINT [PK_T1] PRIMARY KEY CLUSTERED 
(
 [n] ASC
)WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF

Table 2 is setup as:

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
SET ANSI_PADDING ON
GO
CREATE TABLE [dbo].[T2](
 [n] [bigint] NULL,
 [Test] [varchar](4) NOT NULL,
 [Num] [bigint] NULL
) ON [PRIMARY]

GO
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[T2]  WITH CHECK ADD  CONSTRAINT [FK_T2_T1] FOREIGN KEY([n])
REFERENCES [dbo].[T1] ([n])
ON UPDATE CASCADE
GO
ALTER TABLE [dbo].[T2] CHECK CONSTRAINT [FK_T2_T1]

I've populated each with multiple rows.

So in short Table 1 (T1) has a bigint primary key field "n" that's mapped to the child table T2's n field. Cascade Update is set so when T1.n is modified then T2.n is updated also. If I do a simple query and I set the value of T1.n in one of the rows, I can watch the Cascade occur in the Execution Plan. If I set the value of T1.t, the cascade does not occur in the Execution Plan as expected.

In ADO.NET I've added a DataAdapter to my form and had it autogenerate the 4 stored procedures, Update appears as follows for T1: (I added some spacing to help with readability)

SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
ALTER PROCEDURE [dbo].[T1_Update]
(
 @n bigint,
 @t varchar(10),
 @Original_n bigint,
 @Original_t varchar(10)
)
AS
 SET NOCOUNT OFF;
UPDATE [T1] 
SET 
[n] = @n, 
[t] = @t 
WHERE (
([n] = @Original_n) AND 
([t] = @Original_t));

SELECT n, t FROM T1 WHERE (n = @n)

I can execute this stored procedure with:

DECLARE @RC int
DECLARE @n bigint
DECLARE @t varchar(10)
DECLARE @Original_n bigint
DECLARE @Original_t varchar(10)

EXECUTE @RC = [T1_Update] 
   5, '5', 5, '4' -- n, t, Original_n, Original_t

In this the value of n is unchanged and the value of t changes. I would expect to see the same behavior/execution plan as if i just did "UPDATE T1 SET t='4' where n=5 and t='5'". Running this query the Execution Plan is very simple where it goes a CI Seek, Compute Scalar, CI Update, Update.

However, Running the Execute above, the Execution plan shows it scanning table T2 and performing an update on said table even though the value wasn't changed.

I assume this is due to the lines:

UPDATE [T1] 
SET 
[n] = @n, 

Commenting out the [n] = @n from the T1_Update stored procedure, even though the value doesn't change, stops the Cascade Update from firing.

So assuming I want to keep the Cascade Update, is there a way to rewrite/modify the T1_Update stored procedure that was autogenerated so that the Cascade Update is only activated when the value of "n" changes and not activated when the value is unchanged?

EDIT: I removed a Foreign Key relationship on T1 that was referencing itself that was added accidentally when I was setting up this test.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

老子叫无熙 2024-10-08 00:55:05

我谨表示,如果您要更新主键字段,那么它不是主键。对于要成为主键的列,以下三个语句必须为真:

  1. 它绝不能为 NULL。
  2. 它必须是独一无二的。
  3. 它永远不能改变。

前两个通常由数据库服务器强制执行,但大多数不强制执行第三个,而您的设计中似乎违反了第三个。根据我的经验,这是你真的不想做的事情。仅仅因为数据库允许您这样做并不意味着这是一个好主意。这里有龙。

好吧,你已经被警告过。至于如何做你想做的事情 - 我建议你废弃外键约束上的 ON UPDATE CASCADE 选项,而是在父表上创建一个 ON UPDATE 触发器。如果您将其设置为在更新后触发,则旧值和新值都将可用,并且您的代码可以测试以准确查看哪些字段发生了更改并采取适当的操作。您可能还需要将外键设置为在 COMMIT 上触发,而不是在动词时间上触发 - 否则违反约束的短时间内将导致更新失败,即使它会在 COMMIT 时得到纠正被发出。 (我对 SQL Server 不像对其他数据库(例如 Oracle)那样熟悉,并且我不是 100% 肯定您可以将约束执行推迟到 SQL Server 下的 COMMIT 时间。您可能需要做一些挖掘)。

分享并享受。

I respectfully submit that if you're updating your primary key field, it's not a primary key. For a column to be a primary key the following three statements must be true:

  1. It must never be NULL.
  2. It must be UNIQUE.
  3. It must never change.

The first two are commonly enforced by the database server, but most do not enforce the third one, and it's the third one that appears to be violated in your design. In my experience, this is something that you really don't want to do. Just because the database will allow you to do it does not mean it's a good idea. Here there be dragons.

OK, you've been warned. As far as HOW to do what you're trying to do - I suggest that you scrap the ON UPDATE CASCADE option on the foreign key constraint and instead create an ON UPDATE trigger on the parent table. If you set it to fire after the update both the OLD and NEW values will be available and your code can test to see precisely which fields changed and take appropriate action. You may also need to set the foreign key to fire on COMMIT instead of a verb time - otherwise the short period of time that the constraint will be violated will cause the update to fail off, even though it would be corrected by the time a COMMIT was issued. (I'm not as familiar with SQL Server as I am with other databases (e.g. Oracle) and I'm not 100% positive you can defer constraint execution until COMMIT time under SQL Server. You may need to do a little digging).

Share and enjoy.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文