(又翻出来一篇一年多以前的半成品,赶紧给补充完整放出来。)
最近终于得以试用Linq to Sql了,刚开始用,感觉还挺不错,因为一切都显得很简单。也许是我还不太熟悉,所以也有不少的困惑。别的先不说,只说一个:“有状态的”实体类。
Linq to Sql 所创建出来的实体类本身并不是有状态的,有状态的是DataContext。但是正是这个有状态的DataContext,造成了实体类也带上了“状态”。对于一般的桌面应用,或者是结合得比较紧密的Web程序,这还不是什么太大的问题。但是如果我们考虑一个分离得比较充分的N层结构,比如说通过WebService提供数据层或者应用层的服务,这就有问题了。例如有一个ModifyCustomer的WebMethod:
public void ModifyCustomer(Customer customer)
{
//
}
由于Customer是反序列化得来的,并不在DataContext的监管中。因此在调用DataContext.SubmitUpdate()的时候,这些对象是无法进行更新操作的。同时DataContext并没有提供一个直接的方法,来进行Update的操作。
这个问题在英文网上还真是有一些讨论,解决的方法就是用DataContext.Attach。在这之下还有两种方案:
1、
[WebMethod]
public void ModifyCustomer(Customer customer)
{
//
XXDataContext dtx = new XXDataContext();
dtx.Attach(customer, true);
dtx.SubmitChanges();
}
2、
[WebMethod]
public void ModifyCustomer(Customer modified, Customer original)
{
//
XXDataContext dtx = new XXDataContext();
dtx.Attach(modified, original);
dtx.SubmitChanges();
}
然而这两种方法都有各自的一些缺陷。首先说第一种方法,这种方法由于把该实体类强制设置为“已修改”状态,于是就要求该实体类要么有一个时间戳字段,要么就是要求该实体类所有字段都不进行“更新冲突检查”。显然后者是难以接受的,尤其是在一些较为关键的数据表当中,一旦冲突后还允许更新会出现灾难性后果。而前者则要求改动数据库的物理设计,有点为难没有数据库物理设计权限的开发人员。而且即使这样改动,似乎也只能够保证检测出任意的冲突,而在互不相干的改动中是不能认为没有发生冲突的。举个例子:
Client FieldA FieldB TimeStamp (Original) A B 0 User1 A1 1 User2 B2 Conflict!
如果两个客户端都在对Customer表进行修改,其中User1的客户端修改了FieldA,User2的客户端随后修改了FieldB,这个时候用时间戳判断法,就会被系统判定为冲突。因为在强行向DataContext中塞入一个User对象的时候,这个User对象的所有属性并没有有效的“原值”,或者说认为所有属性都被修改了。数据库更不可能有修改前数值的记录,因此对于DataContext来说,只能当作所有值都修改了,因此自然引发了冲突。但是实际上我们可以看出来,这种情况下并没有任何实际上的冲突,是可以更新的。
而第二种方式呢,看起来可以解决上面的这一个问题。但是,即使抛开需要多传一份对象不说,也会造成客户端变得比较复杂。我们可以想象,客户端中调用Web服务获取原始对象的代码,与更新对象的代码很可能根本就不在一个函数内,甚至不在一个调用堆栈上。比如说我们想象一下,客户端实际上也是一个网站,某个网页需要列出当前用户的一些信息,并允许浏览者对某些属性进行一些修改。那么很明显,获取原始用户对象的Web服务调用是在第一次页面访问上的,而更新用户对象则是在第二次访问过程中的。因此,我们必须在第一次访问的时候,将所有可能会被更新的对象都保存到Session里面,以供更新的时候将这些原始对象回传到服务端。这样做会极大地增加客户端设计的复杂度,也不见得很好。
更麻烦的地方是,Linq to sql里面的每一个对象都是设计器自动生成的,那些打上去的标记一不小心就会被丢掉!在我看来,LinqToSql的设计以及自动代码生成过程,对于WebService/WCF的开发来说,还是比较不友好的。不知道各位以为如何?
P.S.: EntityFramework我也小试了一下,有些东西确是比Linq To Sql好用一些,不过似乎这个问题还是没有很好的解决,如果有这方面经验的请赐教。此外,我觉得EntityFramework取消了Log机制,使非常可惜的一件事情,使得一些Sql性能方面的调试不太方便了,恢复到了要用Sql profiler的时代。而我在用Linq to sql做开发的时候,设计了一套能即时开关,并且可设置针对某一特定Session还是所有访问的Trace系统,使得有问题发生时,可以立即在线上查看,而不需要将整个环境(包括数据库相关部分)Dump下来在调试环境下恢复,然后再进行调试(包括Debug和Sql profiler)。可是在EntityFramework下面,这种方式似乎就不太好使了。
___________________
P.S.: 发布到首页还是有Bug,就是保存草稿也算时间。