LINQ更新:找不到行或行已更改

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LINQ更新:找不到行或行已更改相关的知识,希望对你有一定的参考价值。

更新的时候有两行数据总报这个错误,而其它所有行都能正常更新,不知道是什么原因

产生此异常,主要是Linq缓存数据和实际数据库数据不一致的情况造成。解决次问题的情况,主要有几种:
1.比较简单的方法,不使用Linq提供的SubmitChanges()方式提交更改,而直接执行SQL语句,例如:
db.ExecuteCommand("Update [dbo].[LinqTest] SET Age=25 Where ID = @p0", 1);
这样虽然比较方便,但是感觉又回到了直接写SQL的时代,毕竟Linq to SQL的目的,就是为了让我们看不见SQL,避免写复杂的SQL语句,而直接操作实体对象,这样也可以避免程序可读性差、不便于维护。所以除非万不得已,还是不太推荐使用此方法。
2.参考MSDN的资料,采用Linq提供的解决更新冲突的方法,在异常中捕获冲突,然后手动解决冲突:

try

db.SubmitChanges(System.Data.Linq.ConflictMode.ContinueOnConflict);

catch (System.Data.Linq.ChangeConflictException ex)

foreach (System.Data.Linq.ObjectChangeConflict occ in db.ChangeConflicts)

//以下是解决冲突的三种方法,选一种即可
// 使用当前数据库中的值,覆盖Linq缓存中实体对象的值
occ.Resolve(System.Data.Linq.RefreshMode.OverwriteCurrentValues);
// 使用Linq缓存中实体对象的值,覆盖当前数据库中的值
occ.Resolve(System.Data.Linq.RefreshMode.KeepCurrentValues);
// 只更新实体对象中改变的字段的值,其他的保留不变
occ.Resolve(System.Data.Linq.RefreshMode.KeepChanges);

// 这个地方要注意,Catch方法中,我们前面只是指明了怎样来解决冲突,这个地方还需要再次提交更新,这样的话,值 //才会提交到数据库。
db.SubmitChanges();
参考技术A 随笔- 4
文章- 6
评论- 0
前段时间工作中的一个新需求,有机会用到了Linq to SQL。使用后的第一感觉,就是方便很多,也为整个项目节约了一大把的开发时间,甚至代码量也少了很多。不过在程序的实际运行中,始终会遇到一些莫名其妙的异常,最令人不解的,就是System.Data.Linq.ChangeConflictException: Row not found or changed. 。当初凭自己和同事的判断,可能是数据库的数据异常所导致,后来发觉这个异常出现得越来越频繁,于是上MSDN查了查,原来是Linq中一个常见的问题:更新冲突。
这个词说起来比较玄乎,其实再平常不过了。下面可以通过一个简单的例子,来重现这个异常。
建立一个普通的测试表:LinqTest(如图)
在测试表中,插入一条测试数据(如图)
测试代码如下:
在测试代码中,将DataContext的日志定向到Console的输出部分,这样方便我们观察Linq实际执行的SQL语句是什么。重现的时候,我们需要在注释的地方,插入断点进行测试。对于示例中的代码,在正常情况下,是不会有错误的。执行过后,我们可以在Console的输出中,看到实际执行的SQL语句(如图)
再进行第二次调试,首先,恢复Age的数据到以前的样子。下面我们运行到断点处,然后偷偷去SQL Server Management Studio中,手动修改数据,将原始数据中的Age,由24,改为22。然后回到VS2008的IDE,按F5继续运行程序,这个时候,你会发现异常出现了(如图)
再回到Console的输出,查看,执行的SQL语句和刚才的一样。这就是问题的所在,在正常运行状态下,Linq在运行时,会把数据库的数据缓存到实体对象中,这是一种理想化的情况,并且在更新时,Linq会默认把除更新字段外的所有字段,作为Update语句中的Where条件。但是,如果此时有另外的程序,在访问数据库,并修改数据库数据的时候,比如刚才把Age改为22。此时Linq缓存起来的数据和实际数据库中的数据产生了不一致的情况。Linq此时仍然把被修改过的字段,作为Update的Where条件,但是数据库中Age早就被我们改过了,不再是25,Where条件始终匹配不到原有的数据。这时,就会抛出所谓的:System.Data.Linq.ChangeConflictException: Row not found or changed.异常。
产生此异常,主要是Linq缓存数据和实际数据库数据不一致的情况造成。解决次问题的情况,主要有几种:
1.比较简单的方法,不使用Linq提供的SubmitChanges()方式提交更改,而直接执行SQL语句,例如:
db.ExecuteCommand("Update [dbo].[LinqTest] SET Age=25 Where ID = p0", 1);
这样虽然比较方便,但是感觉又回到了直接写SQL的时代,毕竟Linq to SQL的目的,就是为了让我们看不见SQL,避免写复杂的SQL语句,而直接操作实体对象,这样也可以避免程序可读性差、不便于维护。所以除非万不得已,还是不太推荐使用此方法。
2.参考MSDN的资料,采用Linq提供的解决更新冲突的方法,在异常中捕获冲突,然后手动解决冲突:
try

db.SubmitChanges(System.Data.Linq.ConflictMode.ContinueOnConflict);

catch (System.Data.Linq.ChangeConflictException ex)

foreach (System.Data.Linq.ObjectChangeConflict occ in db.ChangeConflicts)

//以下是解决冲突的三种方法,选一种即可
// 使用当前数据库中的值,覆盖Linq缓存中实体对象的值
occ.Resolve(System.Data.Linq.RefreshMode.OverwriteCurrentValues);
// 使用Linq缓存中实体对象的值,覆盖当前数据库中的值
occ.Resolve(System.Data.Linq.RefreshMode.KeepCurrentValues);
// 只更新实体对象中改变的字段的值,其他的保留不变
occ.Resolve(System.Data.Linq.RefreshMode.KeepChanges);

// 这个地方要注意,Catch方法中,我们前面只是指明了怎样来解决冲突,这个地方还需要再次提交更新,这样的话,值 //才会提交到数据库。
db.SubmitChanges();

3. 这个方法也比较简单,也即MSDN中所说的Pessimistic Concurrency Control 。 我们可以来设定哪些字段需要放入Where条件,哪些字段不需要,这样就可以控制更新时候的条件匹配尺度。具体做法,就是在Linq to SQL Designer中,把一些字段的UpdateCheck属性设置为Never,这样,这些字段在更新的时候,就不会再出现在Where条件中了。其实比较推荐的做法,就是在表中设立主键,因为更新的时候,只要把主键作为Where条件,就可以单独的确立一行数据了。把除主键外的字段属性中UpdateCheck设置为Never即可。
关于Linq to SQL中如何管理更改冲突的更多资料,可以在MSDN找到
本文来源:
版权所有 哥签的不是名,是寂寞.....
(请您对文章做出评价)
posted @ 2009-08-21 20:45 阅读(706)
注册用户登录后才能发表评论,请 或 ,。
日一二三四五六26272829303113678910111213141516171819202223242526272829303112345
Copyright
参考技术B 那是你的表被更改过了 ,一般你从新拖一次表就得了!

仅当行已更改时,MySQL 才在更新后触发

【中文标题】仅当行已更改时,MySQL 才在更新后触发【英文标题】:MySQL Trigger after update only if row has changed 【发布时间】:2011-09-11 21:13:24 【问题描述】:

是否有可能仅在数据已真正更改的情况下使用“更新后”触发器。 我知道“新旧”。但是当使用它们时,我只能比较列。 例如“NEW.count OLD.count”。

但我想要类似的东西:运行触发器 if "NEW OLD"

一个例子:

create table foo (a INT, b INT);
create table bar (a INT, b INT);

INSERT INTO foo VALUES(1,1);
INSERT INTO foo VALUES(2,2);
INSERT INTO foo VALUES(3,3);

CREATE TRIGGER ins_sum
    AFTER UPDATE ON foo
    FOR EACH ROW
    INSERT INTO bar VALUES(NEW.a, NEW.b);

UPDATE foo SET b = 3 WHERE a=3;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 0


select * from bar;
+------+------+
| a    | b    |
+------+------+
|    3 |    3 |
+------+------+

关键是,有更新,但没有任何变化。 但是触发器还是跑了。恕我直言,应该有一种方法不会。

我知道我可以使用

如果现在.b 旧.b

对于这个例子。

但是想象一个包含不断变化的列的大表。 您必须比较每一列,如果数据库发生更改,您必须调整触发器。 并且比较硬编码的行的每一列并不“感觉”好:)

加法

正如你在网上看到的那样

匹配行:1 更改:0 警告:0

MySQL 知道该行没有改变。但它不与触发器共享此知识。 像“AFTER REAL UPDATE”之类的触发器或类似的东西会很酷。

【问题讨论】:

如果列发生变化,无论如何您都需要调整触发器,因为它会插入INSERT INTO bar VALUES(NEW.a, NEW.b);。有什么办法可以避免吗?像INSERT INTO bar VALUES(SELECT * FROM foo WHERE…); 这样的东西会起作用吗? @juwens,这个问题你应该得到 10+。令人难以置信的是,他们怎么能如此反直觉地建立这种行为! @zcat,不,您不一定需要在每次表更改时调整触发器。你可以做INSERT INTO bar, SELECT * FROM foo WHERE foo.id = OLD.id,没问题。 附带说明一下,Postgres 的行为也是如此,但您可以在创建触发器语句中添加 WHEN NEW.* IS DISTINCT FROM OLD.* 条件,因此 MySQL 应该实现类似的东西... 【参考方案1】:

作为一种解决方法,您可以使用时间戳(旧的和新的)来检查,当行没有更改时,时间戳是否更新。 (这可能是混淆的根源?因为那个也称为“更新时”,但在没有发生变化时不执行) 一秒钟内的更改将不会执行触发器的那部分,但在某些情况下可能没问题(例如当您有一个应用程序拒绝快速更改时)。

例如,而不是

IF NEW.a <> OLD.a or NEW.b <> OLD.b /* etc, all the way to NEW.z <> OLD.z */ 
THEN  
  INSERT INTO bar (a, b) VALUES(NEW.a, NEW.b) ;
END IF

你可以使用

IF NEW.ts <> OLD.ts 
THEN  
  INSERT INTO bar (a, b) VALUES(NEW.a, NEW.b) ;
END IF

那么您不必每次更新方案时都更改触发器(您在问题中提到的问题。)

编辑:添加完整示例

create table foo (a INT, b INT, ts TIMESTAMP);
create table bar (a INT, b INT);

INSERT INTO foo (a,b) VALUES(1,1);
INSERT INTO foo (a,b) VALUES(2,2);
INSERT INTO foo (a,b) VALUES(3,3);

DELIMITER ///

CREATE TRIGGER ins_sum AFTER UPDATE ON foo
    FOR EACH ROW
    BEGIN
        IF NEW.ts <> OLD.ts THEN  
            INSERT INTO bar (a, b) VALUES(NEW.a, NEW.b);
        END IF;
    END;
///

DELIMITER ;

select * from foo;
+------+------+---------------------+
| a    | b    | ts                  |
+------+------+---------------------+
|    1 |    1 | 2011-06-14 09:29:46 |
|    2 |    2 | 2011-06-14 09:29:46 |
|    3 |    3 | 2011-06-14 09:29:46 |
+------+------+---------------------+
3 rows in set (0.00 sec)

-- UPDATE without change
UPDATE foo SET b = 3 WHERE a = 3;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1  Changed: 0  Warnings: 0

-- the timestamo didnt change
select * from foo WHERE a = 3;
+------+------+---------------------+
| a    | b    | ts                  |
+------+------+---------------------+
|    3 |    3 | 2011-06-14 09:29:46 |
+------+------+---------------------+
1 rows in set (0.00 sec)

-- the trigger didn't run
select * from bar;
Empty set (0.00 sec)

-- UPDATE with change
UPDATE foo SET b = 4 WHERE a=3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

-- the timestamp changed
select * from foo;
+------+------+---------------------+
| a    | b    | ts                  |
+------+------+---------------------+
|    1 |    1 | 2011-06-14 09:29:46 |
|    2 |    2 | 2011-06-14 09:29:46 |
|    3 |    4 | 2011-06-14 09:34:59 |
+------+------+---------------------+
3 rows in set (0.00 sec)

-- and the trigger ran
select * from bar;
+------+------+---------------------+
| a    | b    | ts                  |
+------+------+---------------------+
|    3 |    4 | 2011-06-14 09:34:59 |
+------+------+---------------------+
1 row in set (0.00 sec)

由于 mysql 在处理时间戳方面的行为,它正在工作。 仅当更新发生更改时才会更新时间戳。

文档在这里:https://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html

desc foo;
+-------+-----------+------+-----+-------------------+-----------------------------+
| Field | Type      | Null | Key | Default           | Extra                       |
+-------+-----------+------+-----+-------------------+-----------------------------+
| a     | int(11)   | YES  |     | NULL              |                             |
| b     | int(11)   | YES  |     | NULL              |                             |
| ts    | timestamp | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------+-----------+------+-----+-------------------+-----------------------------+

【讨论】:

我不明白这是怎么回事。您能否更详细地解释一下您的意思。 @derkommissar:我添加了一个例子 有参考链接吗 如果更新频率低于一秒,这将不起作用。时间戳将更改(但更改为相同的值)。您必须使用足够准确的时间戳(6)来跟踪所有更新 正如我在下面的回答中,不要忘记使用 (null 感知运算符)来跟踪从 null 到返回到 null 的更改【参考方案2】:

但是想象一个包含不断变化的列的大表。您必须比较每一列,如果数据库发生更改,您必须调整触发器。并且比较硬编码的每一行并不“感觉”好:)

是的,但这是继续的方式。

附带说明一下,在更新之前预先检查也是一种很好的做法:

UPDATE foo SET b = 3 WHERE a=3 and b <> 3;

在您的示例中,这将使其更新(因此覆盖两行行而不是三行。

【讨论】:

@Denis,这不需要,MySQL检查该值是否真的改变了,如果需要,只启动UPDATE(更新+触发器) .你的检查只会让事情变慢。 @Johan:这是必需的,而 MySQL 不这样做。如果这样做,它就不会尊重 SQL 标准——而且 OP 一开始就不会问他的问题。 @Denis,是的,我在 5.0 和 5.5 中使用我自己的测试触发器进行了检查,它确实做到了。真烦人。 这并不烦人,这很正常,而且实际上是可取的:有时,在更新时触发并不管是否发生更改都会触发它是有用的。间接后果是,如果没有实际发生更改,则由开发人员(或他的 ORM,即使后者从来没有做得最好)来决定是否更新。 @Yohan:你可能会觉得this discussion很有趣。【参考方案3】:

我无法评论,所以请注意,如果您的列支持 NULL 值,OLD.xNEW.x 是不够的,因为

SELECT IF(1&lt;&gt;NULL,1,0)

返回 0 与相同

NULL<>NULL 1<>NULL 0<>NULL 'AAA'<>NULL

所以它不会跟踪 FROM 和 TO NULL 的变化

这个场景下正确的做法是

((OLD.x IS NULL AND NEW.x IS NOT NULL) OR (OLD.x IS NOT NULL AND NEW.x IS NULL) OR (OLD.x<>NEW.x))

【讨论】:

感谢您的宝贵回答/提示!这使得时间戳解决方案更具吸引力,而比较解决方案实际上无法使用。 或者你可以使用COALESCE(),它返回不是NULL的第一个参数。所以你可以写成IF COALESCE(OLD.X,'') &lt;&gt; COALESCE(NEW.X,'') 或者干脆使用mysql空感知比较运算符&lt;=&gt;【参考方案4】:

您可以通过使用NULL-safe equals operator &lt;=&gt; 和negating the result using NOT 比较每个字段来做到这一点。

完整的触发器将变为:

DROP TRIGGER IF EXISTS `my_trigger_name`;

DELIMITER $$

CREATE TRIGGER `my_trigger_name` AFTER UPDATE ON `my_table_name` FOR EACH ROW 
    BEGIN
        /*Add any fields you want to compare here*/
        IF !(OLD.a <=> NEW.a AND OLD.b <=> NEW.b) THEN
            INSERT INTO `my_other_table` (
                `a`,
                 `b`
            ) VALUES (
                NEW.`a`,
                NEW.`b`
            );
        END IF;
    END;$$

DELIMITER ;

(基于different answer of mine。)

【讨论】:

【参考方案5】:

如果有任何行影响新插入,那么它将在数据库中的不同表上更新。

DELIMITER $$

CREATE TRIGGER "give trigger name" AFTER INSERT ON "table name" 
FOR EACH ROW
BEGIN
    INSERT INTO "give table name you want to add the new insertion on previously given table" (id,name,age) VALUES (10,"sumith",24);
END;
$$
DELIMITER ;

【讨论】:

【参考方案6】:

使用以下查询查看哪些行发生了变化:

(select * from inserted) except (select * from deleted)

这个查询的结果应该包含所有不同于旧记录的新记录。

【讨论】:

遗憾的是,这并不能回答问题。更新不会删除行,并且如果更新未更改数据,则 OP 会尝试停止触发器触发(将值为 1 的列更新为值 1 不会更改任何数据,但更新操作仍在运行,因此触发器也是。) 据我了解,UPDATE 查询会将旧值放入已删除的记录集中,将新值放入插入的记录集中。虽然这不会阻止触发器运行,但它可用于阻止触发器执行任何操作,这通常就足够了。 用扩展代码来说明你的答案可能是值得的。但是,请记住,这个问题也有 5 年历史了。 这里引用Microsoft的一段话 更新事务类似于删除操作后跟插入操作;旧行先复制到已删除表中,然后将新行复制到触发器表和插入表中。” 示例代码:在 foo INSERT INTO bar VALUES 更新后创建触发器 ins_sum(从插入中选择 *,从删除中选择 * 除外);【参考方案7】:
MYSQL TRIGGER BEFORE UPDATE IF OLD.a<>NEW.b

USE `pdvsa_ent_aycg`;

DELIMITER $$

CREATE TRIGGER `cisterna_BUPD` BEFORE UPDATE ON `cisterna` FOR EACH ROW

BEGIN

IF OLD.id_cisterna_estado<>NEW.id_cisterna_estado OR OLD.observacion_cisterna_estado<>NEW.observacion_cisterna_estado OR OLD.fecha_cisterna_estado<>NEW.fecha_cisterna_estado

    THEN 

        INSERT INTO cisterna_estado_modificaciones(nro_cisterna_estado, id_cisterna_estado, observacion_cisterna_estado, fecha_cisterna_estado) values (NULL, OLD.id_cisterna_estado, OLD.observacion_cisterna_estado, OLD.fecha_cisterna_estado); 

    END IF;

END

【讨论】:

【参考方案8】:

这里有两个有趣的死胡同(从 MySQL 5.7 开始)-

    new.*old.* 构造无效,MySQL 抱怨 Unknown table 'new'syntax to use near '*,这排除了类似的技巧 select ... from (select (select new.* union select old.*)a having count(*)=2) has_change

    documentation for "ROW_COUNT()" 有一个有用的线索-

对于 UPDATE 语句,affected-rows 值默认是实际更改的行数

确实,更新语句之后,ROW_COUNT() 正确显示了更新后发生更改的行数。但是,更新期间,在触发器内部,ROW_COUNT() = 0 总是。该函数在行级触发器中没有用处,并且截至本答案,MySQL 中没有语句级触发器。

希望这个“空结果”能避免未来的挫败感。

【讨论】:

以上是关于LINQ更新:找不到行或行已更改的主要内容,如果未能解决你的问题,请参考以下文章

powershell Powershell函数用于计算目录中所有文本文件中的所有行或行

具有列或行最大限制的 Pyspark 数据框

更改列中的列宽或行高Excel

使用汇总(或行总计)进行 SQL 透视

UnhandledPromiseRejectionWarning:不清楚警告是针对哪个文件或行[重复]

NSIndexPath 不将项目、部分或行识别为属性