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;

 

但是因为条件限制,我没有能比较关闭这两项后的性能差异,希望知道知道的朋友告诉我一下,谢谢!

 

链接

LINQ那些事(总)

LINQ那些事(9) 解析Table<T>.Attach引发的异常和解决方法

posted @ 2009-12-31 23:24  海南K.K  阅读(1214)  评论(1编辑  收藏  举报