LINQ TO SQL 注意事项
不要将DataContext单例化
单个DataContext最大的问题就是SubmitChanges造成当前线程和数据库的数据不同步。DataContext不是线程安全的对象,由于它会缓存所有的数据操作,当A线程的数据修改到一半,它可能被B线程在调用SubmitChanges时提交了,这个问题在开发时不容易被发现。之前我在项目中用了单例模式创建DataContext,导致出现用户反应“为什么我在这篇文章的评论会跑到别的文章下面去了?!”之类的问题,正是因为这个原因。DataContext是LINQ to SQL框架的主入口点,它拥有所有表的映射让我觉得会占用很多资源,所以我用单例模式创建它。有人通过分析源代码得出结论:“DataContext并没有占用多么庞大的资源,无非就是一个DbConnection和一些映射对象而已。”,我才知道那只是映射,没有任何数据。MSDN上也提示:“请不要试图重用DataContext的实例。每个DataContext都会保持对应一个特定编辑/查询会话的状态(包括标识缓存)。若要获取基于数据库当前状态的新实例,请使用新的DataContext。”
因此,DataContext是不应该被单例化或是静态化的。DataContext是轻量级的资源,创建它不需要很大的开销,最佳实践是为每个操作创建新的DataContext实例。
禁用ObjectTrackingEnabled
将DataContext的ObjectTrackingEnabled设为false,这样减少了要跟踪的项目,可以提高检索时的性能,但禁用对象跟踪时,要特别注意:
(a)只是推荐在查询的情况下禁用;
(b)必须在执行查询前禁用;
(c)禁用之后不能再调用Attach和SubmitChanges方法。
添加IsDbGenerated=true
默认情况下,LINQ添加对象时,数据库中设置的默认值会失效。例如:ModifiedTime是数据库中使用getDate()为默认值的,如果没有为对应的实体属性添加IsDbGenerated=true, 则数据库中的getDate()将失效。
为实体添加RowVersion成员
对于在不同的DataContext之间,使用Attach方法来更新数据,但是Attach方法不能附加一个修改过的实体,除非该实体包含RowVersion成员。
实现RowVersion成员的方法有两种:
(1)为数据库表定义一个timestamp列;
(2)在表主键所对应的实体属性上,定义IsVersion=true特性。
注意,两种方法不能同时使用,否则将抛出InvalidOperationException:成员“System.Data.Linq.Binary timeStamp”和“Int32 XXId”都标记为行版本。
LINQ默认采用“乐观式并发”的策略处理更新数据时的并发冲突。在乐观式并发中,Where子句不只是包含主键字段,还会加入除更新字段外的所有字段,作为Update语句的Where条件。在调用DataContext的SubmitChanges方法时,若Update语句在更新过程中产生了冲突,就会抛出“找不到行或行已更改”的异常。在默认情况下,实体属性的UpdateCheck都设置为UpdateCheck.Always,表示LINQ to SQL将用该属性检查乐观式并发。乐观式并发的参数数量过多,会导致不必要的性能消耗,因此需要修改映射规则,只让部分属性参与到并发检查中。
若在数据表中有timestamp列,实体属性的UpdateCheck会默认设置为UpdateCheck.Never,若使用IsVersion作为更新检查依据,则必须在实体属性上(主键除外)手动设置UpdateCheck.Never来避免更新检查,但是数据库表结构更新,再重新生成dbml的话,手动设置的IsVersion=true和UpdateCheck.Never都将消失不见。所以推荐使用timestamp作为更新检查依据,我想主键列和timestamp列还是各司其职比较好些。为所有数据表创建一个timestamp列,在处理并发的时候,LINQ to SQL就不需要检查所有的字段,这样可以提高系统的性能。
AutoSync特性的作用
对于表主键对应的实体属性,AutoSync会默认设置为AutoSync.OnInsert,它的作用就是Insert一个对象时,当调用DataContext的SubmitChanges方法后,主键对应的属性值会被自动填充。
不需要手动执行DataContext的Dispose方法
DataContext在SubmitChanges方法的finally块中调用了SqlConnection.Close方法;而执行查询的DataContext.ExecuteQuery方法到最后也会调用SqlConnectionManager.ReleaseConnection方法。在我们手动执行DataContext.Dispose时,主要工作已经执行完了,并且SqlConnectionManager.ReleaseConnection方法所执行的内容比SqlConnection.Close方法要多。
因此,在使用LINQ to SQL时,完全没必要手动执行DataContext.Dispose方法或是使用using语句。