LINQ体验(12)——LINQ to SQL语句之对象标识和对象加载

LINQ体验(12)——LINQ to SQL语句之对象标识和对象加载

对象标识

  • 运行库中的对象具有唯一标识。引用同一对象的两个变量实际上是引用此对象的同一实例。你更改一个变量后,可以通过另一个变量看到这些更改。
  • 关系数据库表中的行不具有唯一标识。由于每一行都具有唯一的主键,因此任何两行都不会共用同一键值。

实际上,通常我们是将数据从数据库中提取出来放入另一层中,应用程序在该层对数据进行处理。这就是 LINQ to SQL 支持的模型。将数据作为行从数据库中提取出来时,你不期望表示相同数据的两行实际上对应于相同的行实例。如果您查询特定客户两次,您将获得两行数据。每一行包含相同的信息。

对于对象。你期望在你反复向 DataContext 索取相同的信息时,它实际上会为你提供同一对象实例。你将它们设计为层次结构或关系图。你希望像检索实物一样检索它们,而不希望仅仅因为你多次索要同一内容而收到大量的复制实例。

在 LINQ to SQL 中,DataContext 管理对象标识。只要你从数据库中检索新行,该行就会由其主键记录到标识表中,并且会创建一个新的对象。只要您检索该行,就会将原始对象实例传递回应用程序。通过这种方式,DataContext 将数据库看到的标识(即主键)的概念转换成相应语言看到的标识(即实例)的概念。应用程序只看到处于第一次检索时的状态的对象。新数据如果不同,则会被丢弃。

LINQ to SQL 使用此方法来管理本地对象的完整性,以支持开放式更新。由于在最初创建对象后唯一发生的更改是由应用程序做出的,因此应用程序的意向是很明确的。如果在中间阶段外部某一方做了更改,则在调用 SubmitChanges() 时会识别出这些更改。

以上来自MSDN,的确,看了有点“正规”,下面我用两个例子说明一下。

对象缓存

在第一个示例中,如果我们执行同一查询两次,则每次都会收到对内存中同一对象的引用。很明显,cust1和cust2是同一个对象引用。

Customer cust1 = db.Customers.First(c => c.CustomerID == "BONAP");
Customer cust2 = db.Customers.First(c => c.CustomerID == "BONAP");

下面的示例中,如果您执行返回数据库中同一行的不同查询,则您每次都会收到对内存中同一对象的引用。cust1和cust2是同一个对象引用,但是数据库查询了两次。

Customer cust1 = db.Customers.First(c => c.CustomerID == "BONAP");
Customer cust2 = (
    from o in db.Orders
    where o.Customer.CustomerID == "BONAP"
    select o )
    .First()
    .Customer;

对象加载

延迟加载

在查询某对象时,实际上你只查询该对象。不会同时自动获取这个对象。这就是延迟加载。

例如,您可能需要查看客户数据和订单数据。你最初不一定需要检索与每个客户有关的所有订单数据。其优点是你可以使用延迟加载将额外信息的检索操作延迟到你确实需要检索它们时再进行。请看下面的示例:检索出来CustomerID,就根据这个ID查询出OrderID。

var custs =
     from c in db.Customers
     where c.City == "Sao Paulo"
     select c;
//上面的查询句法不会导致语句立即执行,仅仅是一个描述性的语句,
只有需要的时候才会执行它
foreach (var cust in custs)
{
    foreach (var ord in cust.Orders)
    {
        //同时查看客户数据和订单数据
    }
}

LoadWith 方法:立即加载

你如果想要同时查询出一些对象的集合的方法。LINQ to SQL 提供了 DataLoadOptions用于立即加载对象。方法包括:
LoadWith 方法,用于立即加载与主目标相关的数据。
AssociateWith 方法,用于筛选为特定关系检索到的对象。

使用 LoadWith方法指定应同时检索与主目标相关的哪些数据。例如,如果你知道你需要有关客户的订单的信息,则可以使用 LoadWith 来确保在检索客户信息的同时检索订单信息。使用此方法可仅访问一次数据库,但同时获取两组信息。
在下面的示例中,我们通过设置DataLoadOptions,来指示DataContext在加载Customers的同时把对应的Orders一起加载,在执行查询时会检索位于Sao Paulo的所有 Customers 的所有 Orders。这样一来,连续访问 Customer 对象的 Orders 属性不会触发新的数据库查询。在执行时生成的SQL语句使用了左连接。

NorthwindDataContext db = new NorthwindDataContext();
DataLoadOptions ds = new DataLoadOptions();
ds.LoadWith<Customer>(p => p.Orders);
db.LoadOptions = ds;
var custs = (
     from c in db2.Customers
     where c.City == "Sao Paulo"
     select c);
foreach (var cust in custs)
{
    foreach (var ord in cust.Orders)
    {
        Console.WriteLine("CustomerID {0} has an OrderID {1}.",
            cust.CustomerID,
            ord.OrderID);
    }
}

延迟加载:AssociateWith方法

使用 AssociateWith 方法指定子查询以限制检索的数据量。
在下面的示例中,AssociateWith 方法将检索的 Orders 限制为当天尚未装运的那些 Orders。如果没有此方法,则会检索所有 Orders,即使只需要一个子集。但是生成SQL语句会发现生成了很多SQL语句。

NorthwindDataContext db2 = new NorthwindDataContext();
DataLoadOptions ds = new DataLoadOptions();
ds.AssociateWith<Customer>(
     p => p.Orders.Where(o => o.ShipVia > 1));
db2.LoadOptions = ds;
var custs =
     from c in db2.Customers
     where c.City == "London"
     select c;
foreach (var cust in custs)
{
    foreach (var ord in cust.Orders)
    {
        foreach (var orderDetail in ord.OrderDetails)
        {
           //可以查询出cust.CustomerID, ord.OrderID, ord.ShipVia,
           //orderDetail.ProductID, orderDetail.Product.ProductName
        }
    }
}

立即加载:LoadWith方法和Associate With方法

这个例子说明:使用LoadWith方法来确保在检索客户信息的同时检索订单信息,在检索订单信息的同时检索订单详细信息, 仅仅访问一次数据库。即可以在一个查询中检索许多对象。使用Associate With方法来限制订单详细信息的排序规则。

NorthwindDataContext db2 = new NorthwindDataContext();
DataLoadOptions ds = new DataLoadOptions();
ds.LoadWith<Customer>(p => p.Orders);
ds.LoadWith<Order>(p => p.OrderDetails);
ds.AssociateWith<Order>(
     p => p.OrderDetails.OrderBy(o => o.Quantity));
db2.LoadOptions = ds;
var custs = (
     from c in db2.Customers
     where c.City == "London"
     select c);
foreach (var cust in custs)
{
    foreach (var ord in cust.Orders)
    {
        foreach (var orderDetail in ord.OrderDetails)
        {
           //查询cust.CustomerID, ord.OrderID
           //orderDetail.ProductID, orderDetail.Quantity
        }
    }
}

优先加载

这个例子在Category类里提供了一个LoadProducts分部方法。当产品的类别被加载的时候,就直接优先调用了LoadProducts方法来查询没有货源的产品。

private IEnumerable<Product> LoadProducts(Category category)
{
    //在执行LINQ to SQL的时候,这个LoadProducts分部方法
    //优先加载执行,这里用存储过程也可以. 
    return this.Products
        .Where(p => p.CategoryID == category.CategoryID)
        .Where(p => !p.Discontinued);
}

执行下面的查询时,利用上面方法返回的数据进行下面的操作:

NorthwindDataContext db2 = new NorthwindDataContext();
DataLoadOptions ds = new DataLoadOptions();
ds.LoadWith<Category>(p => p.Products);
db2.LoadOptions = ds;
var q = (
     from c in db2.Categories
     where c.CategoryID < 3
     select c);
foreach (var cat in q)
{
    foreach (var prod in cat.Products)
    {
       //查询cat.CategoryID, prod.ProductID
    }
}

本系列链接:LINQ体验系列文章导航


作者:李永京YJingLee's Blog
出处:http://lyj.cnblogs.com
转载请注明此处,谢谢!

Tag标签: LINQ,LINQ to SQL
posted @ 2008-03-10 15:11 李永京 阅读(2296) 评论(26)  编辑 收藏 所属分类: LINQ

  回复  引用  查看    
#1楼 2008-03-10 15:51 | Howard Queen      
这东西如果有时间耐心学学,肯定好处多多!
  回复  引用  查看    
#2楼 [楼主]2008-03-10 16:12 | 李永京      
@Howard Queen
是的,一段时间没有学习了,现在抓紧学习这个啊。
  回复  引用  查看    
#3楼 2008-03-10 16:26 | yzlhccdec      
LoadWith 方法,用于立即加载与主目标相关的数据。
AssociateWith 方法,用于筛选为特定关系检索到的对象。
这两个和我自己写Join不是一个意思么?
  回复  引用  查看    
#4楼 [楼主]2008-03-10 16:39 | 李永京      
@yzlhccdec
基本上是的,LINQ to SQL执行LoadWith方法时生成的SQL语句就是左连接。但是在LINQ中,LINQ to SQL 提供了 DataLoadOptions,他把这个封装了这两个方法,直接调用这个方法就可以完成join操作。完全面向对象了,还有智能提示,不过在LINQ中还可以使用SQL语句,怎么与ADO.NET结合使用LINQ,下一篇介绍。
  回复  引用  查看    
#5楼 2008-03-10 16:49 | yzlhccdec      
假设我只需要Orders中的某几个字段,LoadWith可是实现么?我刚刚没试出来。
  回复  引用  查看    
#6楼 2008-03-10 17:13 | yzlhccdec      
db2.Customers.Where(p => p.City == "Sao Paulo").Select(p =>new {p, p.Orders}).Select(p=>p.Orders.Select(o=>o.Id))
和第一个例子一模一样。。而且还可以自定义要选取的字段。
我怎么发现这样的话,连Join都不用了。。。。。望赐教。
  回复  引用  查看    
#7楼 2008-03-10 17:23 | 无常      
怎么禁用对象缓存?
因为项目是cs和bs结合的,主键缓存给我添加了不少麻烦,请问怎么禁用这个功能?
  回复  引用  查看    
#8楼 2008-03-10 17:26 | yzlhccdec      
@无常
ObjectTrackingEnabled = false
  回复  引用  查看    
#9楼 [楼主]2008-03-10 17:38 | 李永京      
@yzlhccdec
自定义要选取的字段用Select匿名类型形式:
var custs = (
from c in db2.Customers
where c.City == "Sao Paulo"
select new
{
xxx=c.xxx
yyy=c.yyy
zzz=c.zzz
}
);
具体见前面的帖子http://www.cnblogs.com/lyj/archive/2008/01/23/1049686.html
  回复  引用  查看    
#10楼 [楼主]2008-03-10 17:50 | 李永京      
@yzlhccdec
db2.Customers.Where(p => p.City == "Sao Paulo").Select(p =>new {p, p.Orders}).Select(p=>p.Orders.Select(o=>o.Id))
是用“Lambda表达式”的一种写法
  回复  引用  查看    
#11楼 2008-03-10 21:25 | 无常      
@yzlhccdec
谢谢

再问
XXDataContext db1=new XXDataContext();
XXDataContext db2=new XXDataContext();

Customer cust1 = db.Customers.First(c => c.CustomerID == "BONAP");
Customer cust2 = db2.Customers.First(c => c.CustomerID == "BONAP");

这二次First()会查几次数据库? cust1和cust2是否引用同一对象?

  回复  引用  查看    
#12楼 [楼主]2008-03-10 22:01 | 李永京      
@无常
访问一次数据库。不是引用同一对象,生成SQL语句是:
SELECT TOP (1) [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]
FROM [dbo].[Customers] AS [t0]
WHERE [t0].[CustomerID] = @p0

cust1 and cust2 refer to the same object in memory: False
  回复  引用  查看    
#13楼 2008-03-10 22:40 | 无常      
@李永京
不对吧
刚才测试了一下,是查了二次数据库的

NorthwindDataContext db1 = new NorthwindDataContext();
Customers d1 = db1.Customers.First(p => p.CustomerID == "ALFKI");

NorthwindDataContext db2 = new NorthwindDataContext();
Customers d2 = db2.Customers.First(p => p.CustomerID == "ALFKI");


SELECT TOP (1) [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]
FROM [dbo].[Customers] AS [t0]
WHERE [t0].[CustomerID] = @p0
-- @p0: Input NVarChar (Size = 5; Prec = 0; Scale = 0) [ALFKI]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8

SELECT TOP (1) [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName], [t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]
FROM [dbo].[Customers] AS [t0]
WHERE [t0].[CustomerID] = @p0
-- @p0: Input NVarChar (Size = 5; Prec = 0; Scale = 0) [ALFKI]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.21022.8

  回复  引用  查看    
#14楼 2008-03-10 22:43 | 无常      
@李永京
但想不明的是
这样只访问一次数据库
NorthwindDataContext db = new NorthwindDataContext();
Customers d1 = db.Customers.First(p => p.CustomerID == "ALFKI");
Customers d2 = db.Customers.First(p => p.CustomerID == "ALFKI");

而用where()..first()却会访问二次数据库
NorthwindDataContext db = new NorthwindDataContext();
Customers d3 = db.Customers.Where(p => p.CustomerID == "ALFKI").First();
Customers d4 = db.Customers.Where(p => p.CustomerID == "ALFKI").First();

  回复  引用  查看    
#15楼 [楼主]2008-03-10 23:41 | 李永京      
@无常
呵呵,我还没有测试一下。
下面14楼是在一个文件里测试的吗?
  回复  引用  查看    
#16楼 [楼主]2008-03-10 23:43 | 李永京      
@无常
现在不早了,明天晚上好好研究一下,(*^__^*) 嘻嘻……
不好意思啊
  回复  引用  查看    
#17楼 2008-03-11 10:24 | SZW      
支持一下^_^

另外请教楼主一个问题,是否碰到很多需要执行不确定where字段的情况?是否有比较好的处理方法?比如where p.x==para,这个x是不确定的。我目前想到的只有在where后面加()?:的判断,但是在形成的sql语句里面会为我生成case,效果是可以达到,但是并不是真正我想要的结果,因为效率偏低。
  回复  引用  查看    
#18楼 [楼主]2008-03-17 21:32 | 李永京      
@SZW
另外写一个方法,返回值。
  回复  引用  查看    
#19楼 2008-03-17 23:36 | J. Lin      
  回复  引用  查看    
#20楼 [楼主]2008-03-17 23:58 | 李永京      
@SZW
动态LINQ (第一部分:使用LINQ动态查询库) 中文版
http://blog.joycode.com/scottgu/archive/2008/01/09/113554.aspx
  回复  引用  查看    
#21楼 2008-03-18 11:11 | SZW      
@李永京
@J. Lin
谢谢:)
  回复  引用  查看    
#22楼 [楼主]2008-03-18 12:36 | 李永京      
@SZW
下次我也说说这个。