LINQ那些事儿(6)-对象生命周期管理
为了实现从”LINQ那些事”(1)-(5)中介绍的查询特性,在从数据库获取数据至返回给用户之间,除了O/R Mapping外,LINQ2SQL内建了对象标识缓存和跟踪等服务。本文无意讨论这些服务实现的细节,但是稍微了解这些服务有助于我们更有效的使用LINQ,并且为自己编写LINQ扩展提供参考。
对象标识缓存(identity caching)
让我们来看看对象标识缓存(identity caching)的第一个好处:
var context = GenerateContext(); context.Log = Console.Out; var query1 = (from c in context.Customers select c).Take(100); var watch = new Stopwatch(); watch.Start(); query1.ToList(); watch.Stop(); watch.ElapsedMilliseconds.Dump(); watch.Reset(); watch.Start(); query1.ToList(); watch.Stop(); watch.ElapsedMilliseconds.Dump();
查询Northwnd的Customers表的100条记录,两处的query1.ToList()都执行了两桶的数据库查询操作,但是两处的时间有差别。看看输出结果:
SELECT TOP (100) [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0] .[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax], [t0].[CustomerGuid] FROM [dbo].[Customers] AS [t0] -- Context: SqlProvider(Sql2008) Model: MappedMetaModel Build: 3.5.30729.1 1058 SELECT TOP (100) [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0] .[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax], [t0].[CustomerGuid] FROM [dbo].[Customers] AS [t0] -- Context: SqlProvider(Sql2008) Model: MappedMetaModel Build: 3.5.30729.1 3
第一次查询用了1058毫秒,而第二次查询只花了3毫秒!
这样的情况的前提是发生在同一个DataContext对象上的查询。当从数据库查询返回后,LINQ需要做O/R mapper,这时会先检查缓存中PrimaryKey相同的对象是否已经存在,如果已经存在,则不再对该条数据做映射,避免了重复映射。而作为identity caching的唯一标识就是Primary Key,这就是定义enitity class时必须定义IsPrimaryKey的Column的原因之一。
对象缓存的第二个好处就是避免了同一个DataContext在SubmitChanges时发生optimistic concurrency的情况,相关例子在“LINQ那些事(4)”中已经提过。
很多人会质疑O/R Mapping框架在运行时通过反射来映射对象,会造成性能的降低。这个问题是不可避免的,问题是影响的程度,LINQ2SQL我还没有在具体的项目中应用,问了TerryLee他的项目也没在用,希望有应用经验的朋友能告诉我一下。
改变跟踪(change tracking)
同样的先由一段代码,我们看看change tracking的好处:
var context = GenerateContext(); context.Log = Console.Out; var query = (from c in context.Products select c).First(); query.ProductName = "foo"; context.SubmitChanges();
代码从northwnd数据库中取出一条product记录,修改了ProductName后提交数据库保存,看看输出的SQL语句:
UPDATE [dbo].[Products] SET [ProductName] = @p9 WHERE ([ProductID] = @p0) AND ([ProductName] = @p1) AND ([SupplierID] = @p2) AND ([CategoryID] = @p3) AND ([QuantityPerUnit] = @p4) AND ([UnitPrice] = @p5) AND ([UnitsInStock] = @p6) AND ([UnitsOnOrder] = @p7) AND ([ReorderLevel] = @p8) AND (NOT ([Discontinued] = 1)) -- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [1] -- @p1: Input NVarChar (Size = 4; Prec = 0; Scale = 0) [Chai] -- @p2: Input Int (Size = 0; Prec = 0; Scale = 0) [1] -- @p3: Input Int (Size = 0; Prec = 0; Scale = 0) [1] -- @p4: Input NVarChar (Size = 18; Prec = 0; Scale = 0) [10 boxes x 20 bags] -- @p5: Input Money (Size = 0; Prec = 19; Scale = 4) [18.0000] -- @p6: Input SmallInt (Size = 0; Prec = 0; Scale = 0) [39] -- @p7: Input SmallInt (Size = 0; Prec = 0; Scale = 0) [0] -- @p8: Input SmallInt (Size = 0; Prec = 0; Scale = 0) [10] -- @p9: Input NVarChar (Size = 3; Prec = 0; Scale = 0) [foo] -- Context: SqlProvider(Sql2008) Model: MappedMetaModel Build: 3.5.30729.1
SET语句中只包含已修改的属性,优化了UPDAte语句;Where语句为原始值和数据库值的比较,实现了并发检测。
在返回查询对象之前,DataContext会保存一份对象的copy,并通过change tracking获知对象发生修改,基于这两点DataContext才能生成上面的SQL语句。
在DataContext对象的生命周期内,identity caching和change tracking给我们不少好处,但是这样的好处是以牺牲少许性能为代价的,对于常用的查询-显示模式的应用,可以通过关闭这两个服务来提高性能。
context.ObjectTrackingEnabled = false;
另外一个可提高性能的方法是关闭Lazy-Loading
context.DeferredLoadingEnabled = false;
但是因为条件限制,我没有能比较关闭这两项后的性能差异,希望知道知道的朋友告诉我一下,谢谢!
链接
All the posts in this blog are provided "AS IS" with no warranties, and confer no rights. Except where otherwise noted, content on this site is licensed under a Creative Commons Attribution 2.5 China Mainland License.