none
关于是用LINQ to SQL模拟并发冲突的问题 RRS feed

  • 问题

  • LINQ to SQL的Skip()和Take()运算符都是采用延迟加载的方式执行的。我在测试并发冲突的时候,试着使用这两个运算符来获取若干记录。因为需要模拟出两个用户的并发修改操作,所以使用了两个自定义数据上下文。就是通过Skip()和Take()这两个运算符来获取测试数据的。但是,因为这两个运算符是延迟加载的,所以当一个用户更新了数据之后,另一个用户实际上是在前一个用户更新后才去读取数据库的。这样一来肯定不会产生并发冲突的了。请问有没有什么办法让Skip()和Take()禁用延迟加载呢?下面是测试代码:

    // 用户A和用户B同时获取相同的记录。
    var UserACustomers = dbUserA.Customers.Skip(23).Take(7);
    var UserBCustomers = dbUserB.Customers.Skip(23).Take(7);
    
    // 用户B首先修改数据并更新到数据。
    foreach (var CustomerObject in UserBCustomers)
    {
      CustomerObject.ContactName = "UserB";
      CustomerObject.ContactTitle = "UserB";
      CustomerObject.Country = "UserB";
      CustomerObject.City = "UserB";
      CustomerObject.Address = "UserB";
    }
    dbUserB.SubmitChanges();
    
    // 然后才是用户A修改数据,并尝试更新到数据库,此时将引发并发冲突。
    foreach (var CustomerObject in UserACustomers)
    {
      CustomerObject.ContactName = "UserA";
      CustomerObject.ContactTitle = "UserA";
      CustomerObject.Country = "UserA";
      CustomerObject.City = "UserA";
      CustomerObject.Address = "UserA";
    }
    dbUserA.SubmitChanges(ConflictMode.FailOnFirstConflict);
    
    
    2010年9月17日 7:49

答案

  • 关于此问题交流的帖子可以在这里查看:http://topic.csdn.net/u/20100917/15/b5dc1003-1607-4cf8-a100-500feb608e23.html

    此问题是通过和CSDN的网友交流获得解决的,首先对参与和关注此问题的网友表示感谢,特别是foren_whb给予了热心地、直接地帮助!谢谢你们!望日后继续进行着广泛地深入的交流!

    虽然说,谁也不想遇到并发冲突这种情况,但这却是一个必然会发生的事情,因此,学习掌握它的解决办法还是很有必要的。我们自己当然可以自定义并发冲突情况的处理办法了,但LINQ to SQL本身在这个问题上也有它自己的解决办法。于是自己就想来测试测试它的这个解决办法。在这之前,首先得要模拟出并发冲突吧!然后,就根据自己所掌握的LINQ to SQL知识动手编写了一个小小的测试代码。代码如下:

    // 用户A和用户B同时获取相同的记录。
    var UserACustomers = dbUserA.Customers.Skip(23).Take(7);
    var UserBCustomers = dbUserB.Customers.Skip(23).Take(7);
    
    // 用户B首先修改数据并更新到数据。
    foreach (var CustomerObject in UserBCustomers)
    {
     CustomerObject.ContactName = "UserB";
     CustomerObject.ContactTitle = "UserB";
     CustomerObject.Country = "UserB";
     CustomerObject.City = "UserB";
     CustomerObject.Address = "UserB";
    }
    dbUserB.SubmitChanges();
    
    // 然后才是用户A修改数据,并尝试更新到数据库,此时将引发并发冲突。
    foreach (var CustomerObject in UserACustomers)
    {
     CustomerObject.ContactName = "UserA";
     CustomerObject.ContactTitle = "UserA";
     CustomerObject.Country = "UserA";
     CustomerObject.City = "UserA";
     CustomerObject.Address = "UserA";
    }
    dbUserA.SubmitChanges(ConflictMode.FailOnFirstConflict);
    
    
    


    通常情况下,如果按照这个代码来执行的话,应该是会引发并发冲突的。然而,事实却并非像想象的那样。如果程序是按着代码编写的顺序来执行的话,那在两个数据上下文调用Skip()和Take()运算符的时候,就应该获取相关数据记录的。但是,LINQ to SQL却有一个延迟执行的问题,而这个Skip()和Take()运算符刚好就是延迟执行的运算符。延迟执行是个什么概念呢?以上面的代码为例,当我们调用这两个运算符的时候,实际上并没有真正的从数据库中获取数据,当代码运行到foreach数据的时候,此时LINQ to SQL才去数据库中读取数据。这样一来就存在这样的一个问题了:
    之所以引发并发冲突,是因为用户在读取到要更新的数据记录后、提交更新操作之前的这段时间里,数据库中的记录已经被修改了。既然Skip()和Take()运算符是延迟执行的,这样当用户B把数据更新到数据库之后,用户A才从数据库获取数据,这样一来,无论如何都不会引发并发冲突了。一时间竟没了主意。现在想起来,可真是够笨的了!
    后来在论坛上发帖子,希望其他的网友能够对这个问题有研究!
    后来CSDN网友foren_whb指出可以使用ToList()这个运算符,说这个运算符是理解执行的。
    我把上面的代码修改为这样的:

    var UserACustomers = dbUserA.Customers.Skip(23).Take(7).ToList();
    var UserBCustomers = dbUserB.Customers.Skip(23).Take(7).ToList();
    
    


    终于搞出了一个并发冲突了!^_^
    想让它出来的时候,它还不出来;不想让它出来的时候,老跑出来捣乱,真是可恶至极!

    后来,又被我想到了另外一个解决办法:
    在LINQ to SQL中所修改的数据要向被保存到数据库中,必须调用DataContext.SubmitChanges()这个方法,否则的话,所做的修改仅仅是内存中的副本,而并非数据库中的数据。既然如此,在调用foreach修改数据之后,暂且不要调用这个方法,这样一来,用户A和用户B修改的数据都不会被提交的。直到用户A也调用foreach语句之后,再提交更新操作。这样也能引发并发冲突!代码如下:

    // 用户A和用户B同时获取相同的记录。
    var UserACustomers = dbUserA.Customers.Skip(23).Take(7);
    var UserBCustomers = dbUserB.Customers.Skip(23).Take(7);
    
    // 用户B首先修改数据并更新到数据。
    foreach (var CustomerObject in UserBCustomers)
    {
     CustomerObject.ContactName = "UserB";
     CustomerObject.ContactTitle = "UserB";
     CustomerObject.Country = "UserB";
     CustomerObject.City = "UserB";
     CustomerObject.Address = "UserB";
    }
    
    // 然后才是用户A修改数据,并尝试更新到数据库,此时将引发并发冲突。
    foreach (var CustomerObject in UserACustomers)
    {
     CustomerObject.ContactName = "UserA";
     CustomerObject.ContactTitle = "UserA";
     CustomerObject.Country = "UserA";
     CustomerObject.City = "UserA";
     CustomerObject.Address = "UserA";
    }
    
    dbUserB.SubmitChanges();
    dbUserA.SubmitChanges(ConflictMode.FailOnFirstConflict);
    
    
    


    上面的代码是在用户A和用户都修改完毕数据之后,才提交他们各自上下文的SubmitChanges()方法,用户B的数据被成功更新,但用户A的更新却引发了并发冲突!^_^

    另外,在这个问题的交流中,使我明白了两个概念【延迟加载】和【延迟执行】。
    所谓【延迟加载】指的是关系映射中相关联的数据,是在需要的时候加载呢?还是一次性的加载出来呢?
    比如说,Northwind数据库中的客户(Customers)可能会有多个订单(Orders),当我们检索客户的时候,是否一次性的把此客户的所有订单都检索出来呢?如果不是,也就是说在需要的时候才去检索某个客户的订单记录,这便是延迟加载了。而立即加载的意思,就是说在检索客户的时候,一次性的把关联的订单记录都检索出来咯。这个延迟加载,或者立即加载都是有各自的好处和缺点的。
    可以通过DataContext.DeferredLoadingEnabled来设置是否延迟加载。
    而【延迟执行】的意思就是:不会立即执行,而是等到需要数据的时候,它才会去执行。
    比如,本例中的Skip()和Take()运算符,当我们调用的时候它就没有获取数据,而是到foreach语句的时候,才去获取。
    向声明性查询基本上都是延迟执行的。
    呵呵,这两个概念之前读过,但是真是没有好好的区别它们。以至于我在提问的时候,把延迟执行,还说成是延迟加载呢! 

    2010年9月21日 8:18

全部回复

  • 从测试代码来看,很明显是两个用户同时获取数据的。并且用户A在用户B更新之后,才尝试提交更新的。但是,Skip()和Take()运算符是采用延迟加载的,这样一来,用户A和用户B获取数据的时间,实际是在foreach语句那里。因此,用户A获取数据的时间实际上是在用户B之后。这样无论如何也不可能引发并发冲突的!请大虾们赐教!!!
    2010年9月17日 7:57
  • 啊!没一个人回复呀!

    2010年9月19日 1:39
  • 关于此问题交流的帖子可以在这里查看:http://topic.csdn.net/u/20100917/15/b5dc1003-1607-4cf8-a100-500feb608e23.html

    此问题是通过和CSDN的网友交流获得解决的,首先对参与和关注此问题的网友表示感谢,特别是foren_whb给予了热心地、直接地帮助!谢谢你们!望日后继续进行着广泛地深入的交流!

    虽然说,谁也不想遇到并发冲突这种情况,但这却是一个必然会发生的事情,因此,学习掌握它的解决办法还是很有必要的。我们自己当然可以自定义并发冲突情况的处理办法了,但LINQ to SQL本身在这个问题上也有它自己的解决办法。于是自己就想来测试测试它的这个解决办法。在这之前,首先得要模拟出并发冲突吧!然后,就根据自己所掌握的LINQ to SQL知识动手编写了一个小小的测试代码。代码如下:

    // 用户A和用户B同时获取相同的记录。
    var UserACustomers = dbUserA.Customers.Skip(23).Take(7);
    var UserBCustomers = dbUserB.Customers.Skip(23).Take(7);
    
    // 用户B首先修改数据并更新到数据。
    foreach (var CustomerObject in UserBCustomers)
    {
     CustomerObject.ContactName = "UserB";
     CustomerObject.ContactTitle = "UserB";
     CustomerObject.Country = "UserB";
     CustomerObject.City = "UserB";
     CustomerObject.Address = "UserB";
    }
    dbUserB.SubmitChanges();
    
    // 然后才是用户A修改数据,并尝试更新到数据库,此时将引发并发冲突。
    foreach (var CustomerObject in UserACustomers)
    {
     CustomerObject.ContactName = "UserA";
     CustomerObject.ContactTitle = "UserA";
     CustomerObject.Country = "UserA";
     CustomerObject.City = "UserA";
     CustomerObject.Address = "UserA";
    }
    dbUserA.SubmitChanges(ConflictMode.FailOnFirstConflict);
    
    
    


    通常情况下,如果按照这个代码来执行的话,应该是会引发并发冲突的。然而,事实却并非像想象的那样。如果程序是按着代码编写的顺序来执行的话,那在两个数据上下文调用Skip()和Take()运算符的时候,就应该获取相关数据记录的。但是,LINQ to SQL却有一个延迟执行的问题,而这个Skip()和Take()运算符刚好就是延迟执行的运算符。延迟执行是个什么概念呢?以上面的代码为例,当我们调用这两个运算符的时候,实际上并没有真正的从数据库中获取数据,当代码运行到foreach数据的时候,此时LINQ to SQL才去数据库中读取数据。这样一来就存在这样的一个问题了:
    之所以引发并发冲突,是因为用户在读取到要更新的数据记录后、提交更新操作之前的这段时间里,数据库中的记录已经被修改了。既然Skip()和Take()运算符是延迟执行的,这样当用户B把数据更新到数据库之后,用户A才从数据库获取数据,这样一来,无论如何都不会引发并发冲突了。一时间竟没了主意。现在想起来,可真是够笨的了!
    后来在论坛上发帖子,希望其他的网友能够对这个问题有研究!
    后来CSDN网友foren_whb指出可以使用ToList()这个运算符,说这个运算符是理解执行的。
    我把上面的代码修改为这样的:

    var UserACustomers = dbUserA.Customers.Skip(23).Take(7).ToList();
    var UserBCustomers = dbUserB.Customers.Skip(23).Take(7).ToList();
    
    


    终于搞出了一个并发冲突了!^_^
    想让它出来的时候,它还不出来;不想让它出来的时候,老跑出来捣乱,真是可恶至极!

    后来,又被我想到了另外一个解决办法:
    在LINQ to SQL中所修改的数据要向被保存到数据库中,必须调用DataContext.SubmitChanges()这个方法,否则的话,所做的修改仅仅是内存中的副本,而并非数据库中的数据。既然如此,在调用foreach修改数据之后,暂且不要调用这个方法,这样一来,用户A和用户B修改的数据都不会被提交的。直到用户A也调用foreach语句之后,再提交更新操作。这样也能引发并发冲突!代码如下:

    // 用户A和用户B同时获取相同的记录。
    var UserACustomers = dbUserA.Customers.Skip(23).Take(7);
    var UserBCustomers = dbUserB.Customers.Skip(23).Take(7);
    
    // 用户B首先修改数据并更新到数据。
    foreach (var CustomerObject in UserBCustomers)
    {
     CustomerObject.ContactName = "UserB";
     CustomerObject.ContactTitle = "UserB";
     CustomerObject.Country = "UserB";
     CustomerObject.City = "UserB";
     CustomerObject.Address = "UserB";
    }
    
    // 然后才是用户A修改数据,并尝试更新到数据库,此时将引发并发冲突。
    foreach (var CustomerObject in UserACustomers)
    {
     CustomerObject.ContactName = "UserA";
     CustomerObject.ContactTitle = "UserA";
     CustomerObject.Country = "UserA";
     CustomerObject.City = "UserA";
     CustomerObject.Address = "UserA";
    }
    
    dbUserB.SubmitChanges();
    dbUserA.SubmitChanges(ConflictMode.FailOnFirstConflict);
    
    
    


    上面的代码是在用户A和用户都修改完毕数据之后,才提交他们各自上下文的SubmitChanges()方法,用户B的数据被成功更新,但用户A的更新却引发了并发冲突!^_^

    另外,在这个问题的交流中,使我明白了两个概念【延迟加载】和【延迟执行】。
    所谓【延迟加载】指的是关系映射中相关联的数据,是在需要的时候加载呢?还是一次性的加载出来呢?
    比如说,Northwind数据库中的客户(Customers)可能会有多个订单(Orders),当我们检索客户的时候,是否一次性的把此客户的所有订单都检索出来呢?如果不是,也就是说在需要的时候才去检索某个客户的订单记录,这便是延迟加载了。而立即加载的意思,就是说在检索客户的时候,一次性的把关联的订单记录都检索出来咯。这个延迟加载,或者立即加载都是有各自的好处和缺点的。
    可以通过DataContext.DeferredLoadingEnabled来设置是否延迟加载。
    而【延迟执行】的意思就是:不会立即执行,而是等到需要数据的时候,它才会去执行。
    比如,本例中的Skip()和Take()运算符,当我们调用的时候它就没有获取数据,而是到foreach语句的时候,才去获取。
    向声明性查询基本上都是延迟执行的。
    呵呵,这两个概念之前读过,但是真是没有好好的区别它们。以至于我在提问的时候,把延迟执行,还说成是延迟加载呢! 

    2010年9月21日 8:18