这是前文《N层研习记录01:试图通过Boolean参数控制并发冲突的检查方式(LINQ to SQL)》用到的测试代码。只是包含了其中最重要的部分,如果要想获取完整的代码,可以通过以下地址进行下载:
下载地址1:http://download.youkuaiyun.com/source/2747401
下载地址2:http://u.115.com/file/f26716bcc2
以上地址如果均不能下载,请留言通知我!
测试代码的服务端使用的是WCF,客户端是一个控制台应用程序。其中也包含了测试中的一些记录,可参看代码中的注释部分。
下面这段代码是服务实现的主要代码。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.ServiceModel; using NLayer.Contracts; using NLayer.Model; using System.Data.Linq; using System.IO; namespace NLayer.Services { public class NorthwindServices : INorthwindService { /// <summary> /// 获取系统中所有的客户数据。 /// </summary> /// <returns></returns> public IEnumerable<Customer> RetrieveAllCustomers() { NorthwindExDataContext db = new NorthwindExDataContext(); StreamWriter Writer = File.AppendText(@"C:/LINQ/NLayer.log"); db.Log = Writer; Writer.WriteLine("-------------------------------------------------"); Writer.WriteLine("NorthwindServices.RetrieveAllCustomers()"); Writer.WriteLine(); var AllCustomers = from CustomerObject in db.Customers select CustomerObject; Writer.Close(); return AllCustomers.AsEnumerable(); } /// <summary> /// 获取指定编号的客户数据。 /// </summary> /// <param name="CustomerID"></param> /// <returns></returns> public Customer RetrieveCustomer(string CustomerID) { NorthwindExDataContext db = new NorthwindExDataContext(); StreamWriter Writer = File.AppendText(@"C:/LINQ/NLayer.log"); db.Log = Writer; Writer.WriteLine("-------------------------------------------------"); Writer.WriteLine("NorthwindServices.RetrieveCustomer()"); Writer.WriteLine(); var QueryCustomer = from CustomerObject in db.Customers where CustomerObject.CustomerID == CustomerID select CustomerObject; var Result = QueryCustomer.First(); Writer.Close(); return Result; } /// <summary> /// 删除指定的客户对象。 /// </summary> /// <param name="DeletedCustomer"></param> public void DeleteCustomer(Customer DeletedCustomer) { NorthwindExDataContext db = new NorthwindExDataContext(); StreamWriter Writer = File.AppendText(@"C:/LINQ/NLayer.log"); db.Log = Writer; Writer.WriteLine("-------------------------------------------------"); Writer.WriteLine("NorthwindServices.DeleteCustomer()"); Writer.WriteLine(); try { db.Customers.Attach(DeletedCustomer, false); // 如果根据MSDN文档的说法,这里设置为false。 // 不过,到现在我可以说,这个地方设置一个什么样的参数值, // 都不是用来控制LINQ to SQL采用哪种方式执行删除和更新操作的。 // 这个方法删除客户也不是基于原始值的方式,而是基于时间戳和主键方式删除的。 // 另外此方法将会引发异常,因为可能所指定的客户还包含有一些订单的。 db.Customers.DeleteOnSubmit(DeletedCustomer); db.SubmitChanges(); } catch (Exception ex) { Writer.WriteLine(ex.Message); } finally { Writer.Close(); } } /// <summary> /// 试图使用基于时间戳的方式更新指定的客户对象。 /// </summary> /// <param name="CustomerObject"></param> public void UpdateCustomerByRowVersion(Customer CustomerObject) { NorthwindExDataContext db = new NorthwindExDataContext(); StreamWriter Writer = File.AppendText(@"C:/LINQ/NLayer.log"); db.Log = Writer; Writer.WriteLine("-------------------------------------------------"); Writer.WriteLine("NorthwindServices.UpdateCustomerByRowVersion()"); Writer.WriteLine(); try { db.Customers.Attach(CustomerObject, true); // 如果这里设置为false的话,那等于就是告诉给LINQ to SQL这个对象是没有被修改的, // 而且在后面的代码中也没有修改过这个对象, // 这样一来在当前数据上下文跟踪的对象中就没有一个对象被修改了, // 既然如此也就没有必要生成相应的UPDATE的SQL语句了。 // 怪不得测试中始终无法看到相应的SQL命令。哎! // 不过这里虽然设置为true了,实际所生成的更新命令却不是根据时间戳来更新的, // 而是根据客户的编号来更新的,这是为什么呢?可能是因为我把实体类的版本列成员给修改为普通成员了。 // 到底什么情况下会使用基于时间戳的方式更新数据呢? // 如果实体类有主键列却没有版本列的话,就算此时数据库中仍然具有版本列, // 也同样不会使用基于时间戳的方式,而是仅仅基于主键的方式。 // 如果实体类连主键也没有呢?或者实体类的版本列并非对应于数据库中的版本列呢? // 如果实体类一个主键也没有设置的话,无法在其上执行Create、Update或Delete操作。 // 如果此时要是设置一个版本列,但没有主键列,又会怎样呢? // 呵呵,没怎么样,有版本列又能怎样,只要没有主键列,就是不行。 // 那如果主键列属性不对应于数据表的主键列呢? // 如果在实体类中设置的主键属性不对应于数据表中的主键列,同样可以更新数据。 // 只是是基于实体类设置的主键属性和版本列来更新的,而并非是数据表中的主键。 // 通过以上的测试表明:所谓的基于主键和版本列更新数据, // 其实是基于实体类的主键列属性和版本列属性,而并非数据表中的主键和版本列。 // 因此,只有正确的映射主键列和版本列才可以确保万无一失。 // 另外,我想删除操作应该也是这么一回事吧! db.SubmitChanges(); } catch (Exception ex) { Writer.WriteLine(ex.Message); } finally { Writer.Close(); } } /// <summary> /// 采用基于修改部分属性值的方式更新数据。 /// /// </summary> /// <param name="OriginalCustomer"></param> /// <param name="ContactName"></param> /// <param name="ContactTitle"></param> public void UpdateCustomerContact(Customer OriginalCustomer, string ContactName, string ContactTitle) { NorthwindExDataContext db = new NorthwindExDataContext(); StreamWriter Writer = File.AppendText(@"C:/LINQ/NLayer.log"); db.Log = Writer; Writer.WriteLine("-------------------------------------------------"); Writer.WriteLine("NorthwindServices.UpdateCustomerContact()"); Writer.WriteLine(); try { db.Customers.Attach(OriginalCustomer, false); OriginalCustomer.ContactName = ContactName; OriginalCustomer.ContactTitle = ContactTitle; db.SubmitChanges(); // 缺点:此方式更新数据的灵活性不高,不过也可以为每个列属性都定义一个参数。 // 呵呵,如果这样做的话,还不如使用下面那种基于完整实体的方式更新数据呢。 // 经过测试此代码实际上也是基于时间戳和主键的方式更新数据的。 // 实体类不能没有主键列属性,如果没有的话就无法执行增加、删除和更新的操作。 // 虽然可以没有版本列属性,但即使没有这个属性,那也是基于主键方式进行数据的更新, // 而并非那种基于原始值的方式呀。 // 不过,如果主键属性,而没有版本列属性,并且主键属性不对应于数据表中的主键列, // 那又会生成怎样的SQL呢? // 真是太恐怖了。仍然是采用基于主键列属性的方式来更新数据的。 // 如果所设置的主键列属性映射到数据表中的那个列可以包含重复值的话, // 那么更新数据的时候可能就会同时更新若干行。 // 比如,我测试时是将【城市】列属性设置为主键列的, // 结果竟然把居住在【柏林】的所有客户的姓名和联系方式都更新成为一样的了。 // 哎!再一次体会到正确映射主键列属性的重要性! // 如果映射错误的话,会死的很难看的。 // 另外,通过这些测试充分说明:LINQ to SQL在生成SQL命令的时候, // 是根据对象模型中的信息来设置的,而并非是根据数据库设置的。 // 这也是合情合理的,因此最好使对象模型正确的映射到数据库,否则可能会出现意想不到的效果。 } catch (Exception ex) { Writer.WriteLine(ex.Message); } finally { Writer.Close(); } } /// <summary> /// 基于完整实体的方式更新数据。 /// </summary> /// <param name="OriginalCustomer"></param> /// <param name="NewCustomer"></param> public void UpdateCustomer(Customer OriginalCustomer, Customer NewCustomer) { NorthwindExDataContext db = new NorthwindExDataContext(); StreamWriter Writer = File.AppendText(@"C:/LINQ/NLayer.log"); db.Log = Writer; Writer.WriteLine("-------------------------------------------------"); Writer.WriteLine("NorthwindServices.UpdateCustomer()"); Writer.WriteLine(); try { db.Customers.Attach(NewCustomer, OriginalCustomer); db.SubmitChanges(); } catch (Exception ex) { Writer.WriteLine(ex.Message); // 更新一直出现异常情况。可是初开始更新的时候就没有异常情况, // 现在终于明白是什么原因了。 // 原来我第一次更新数据的时候所使用的数据表及实体类是没有时间戳列的, // 而这里测试时使用的数据表有时间戳列,而且映射的实体类也有版本列。 // 光是这样并不会引发异常,引发异常的原因是我下意识的以为时间戳列是由数据库生成的, // 因此在创建新的对象是,并没有将原始对象的时间戳列的属性值赋值给新对象的时间戳列属性, // 这样一来原始对象和新建对象的版本属性的值当然也就不相同了。 // 我想,可能因为这样,在附加的时候就引发了异常。还是捕获异常好呀! // 接着,我就将原始对象的版本列属性值赋给新建对象的版本列属性,这次自然也就成功执行了。 // 另外经过这个测试我感觉:到底是使用基于时间戳的方式,还是基于原始值的方式更新数据, // 这个选择貌似完全是由LINQ to SQL来自动选择的。 // 我一开始就认为这种基于完整实体对象的方式更新数据应该是很自然的采用基于原始属性值的方式更新数据, // 但是根据捕获到的SQL语句来看,它确实基于时间戳和主键的方式更新数据的。 // 莫非我们真的无法控制到底该采用哪种方式更新数据吗? // 怎么到现在为止,所有的测试都是基于时间戳和主键列来执行的呢? // 如果将实体类的版本列成员取消为普通成员的话,那也是根据主键列来执行的, // 这样一来岂不是无法很好的控制并发冲突吗? // 到目前为止,至少可以断定的是,数据表最好还是添加一个版本列, // 同时在对象模型中也映射一个版本列属性,这样是最简单的控制并发冲突的做法。 // 可问题是,如何实现基于原始值的方式更新数据呢? } finally { Writer.Close(); } } } }
下面是客户端的测试代码,运行此代码时可能需要将部分代码注释掉,否则可能会产生编译时错误。
using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading; using NLayer.Client.NorthwindServiceReference; namespace NLayer.Client { class Program { static void Main(string[] args) { // ************************************************* // 删除数据:尝试基于时间戳的方式删除数据。 // ************************************************* NorthwindServicesClient Client = new NorthwindServicesClient(); Customer CustomerObject = Client.RetrieveCustomer("ALFKI"); Client.DeleteCustomer(CustomerObject); // ************************************************* // 更新数据:尝试基于时间列的方式更新数据。 // ************************************************* NorthwindServicesClient Client = new NorthwindServicesClient(); Customer CustomerObject = Client.RetrieveCustomer("ALFKI"); CustomerObject.ContactName = CustomerObject.ContactName + "1"; CustomerObject.ContactTitle = CustomerObject.ContactTitle + "1"; Client.UpdateCustomerByRowVersion(CustomerObject); // ************************************************* // 更新数据:尝试基于部分列属性修改的方式更新数据。 // ************************************************* NorthwindServicesClient Client = new NorthwindServicesClient(); Customer CustomerObject = Client.RetrieveCustomer("ALFKI"); Client.UpdateCustomerContact(CustomerObject, "Maria Anders", "Sales Representative"); // ************************************************* // 更新数据:尝试基于完整实体的方式更新数据。 // ************************************************* NorthwindServicesClient Client = new NorthwindServicesClient(); Customer OriginalCustomer = Client.RetrieveCustomer("ALFKI"); Customer NewCustomer = new Customer() { Address = OriginalCustomer.Address, City = OriginalCustomer.City, ContactName = OriginalCustomer.ContactName, ContactTitle = OriginalCustomer.ContactTitle, Country = OriginalCustomer.Country, CustomerID = OriginalCustomer.CustomerID, Fax = OriginalCustomer.Fax, Phone = OriginalCustomer.Phone, PostalCode = OriginalCustomer.PostalCode, Region = OriginalCustomer.Region, Version = OriginalCustomer.Version }; NewCustomer.ContactName = NewCustomer.ContactName + "2"; NewCustomer.ContactTitle = NewCustomer.ContactTitle + "2"; Client.UpdateCustomer(OriginalCustomer, NewCustomer); } } }