LINQ to SQL 技巧和窍门






4.89/5 (22投票s)
本文介绍了一些 LINQ to SQL 的技巧和窍门。
引言
在本文中,我将介绍一些我在使用LINQ时发现的技巧。
源代码
源代码包含Web应用程序,其中包含本文所述所有技巧的代码示例。要运行该应用程序,您需要AdventureWorks
数据库,该数据库可以在此处下载。您还需要在配置文件中更新连接字符串,使其具有正确的值。
为保持示例简单,我只将3个表导入了LINQ模型。下面是模型架构

LINQ to SQL 技巧和窍门
加载选项
默认情况下,当您加载一个实体时,所有关联都不会被加载。默认情况下,所有关联都是延迟加载的,换句话说,只有在需要/访问时才会加载。让我们来看下面的代码
using (AdventureWorksDataContext db = new AdventureWorksDataContext())
{
//First query is executed. The query returns one product.
Product product = db.Products.Where(o =>
o.ProductSubcategoryID != null).FirstOrDefault();
//Second query is executed. The query returns
//the subcategory for the previous product.
if (product.ProductSubcategory != null)
{
return product.ProductSubcategory.Name;
}
return "";
}
如您所见,当访问ProductSubcategory
时,会发生第二次查询。这就是所谓的延迟加载。为了消除这种情况,您可以使用加载选项。加载选项会指导LINQ需要为某个实体加载哪些关联数据。接下来的示例使用了加载选项。
using (AdventureWorksDataContext db = new AdventureWorksDataContext())
{
DataLoadOptions loadOptions = new DataLoadOptions();
loadOptions.LoadWith<Product>(o => o.ProductSubcategory);
db.LoadOptions = loadOptions;
//One query is executed. The query returns one product
//and its subcategory using a left outer join
Product product = db.Products.Where(o =>
o.ProductSubcategoryID != null).FirstOrDefault();
if (product.ProductSubcategory != null)
{
return product.ProductSubcategory.Name;
}
return "";
}
请注意,第二个示例只执行了一个查询。
延迟加载
通过设置DeferredLoadingEnabled
属性,可以在数据上下文中禁用或启用延迟加载。默认值为true
,因此所有关联都将延迟加载。让我们看看禁用延迟加载的示例
using (AdventureWorksDataContext db = new AdventureWorksDataContext())
{
db.DeferredLoadingEnabled = false;
//First query is executed. The query returns one product.
Product product = db.Products.Where(o =>
o.ProductSubcategoryID != null).FirstOrDefault();
//ProductSubcategory will always be null. If DeferredLoadingEnabled would
//be true then this would cause a second query to the database and
//ProductSubcategory would receive a value
if (product.ProductSubcategory != null)
{
//this will never be executed
return product.ProductSubcategory.Name;
}
return "";
}
当启用延迟加载时,常常会发生一个错误。当您尝试访问关联并且数据上下文对象已经被释放时,您将收到“无法访问已释放的对象”异常。
Product product = null;
using (AdventureWorksDataContext db = new AdventureWorksDataContext())
{
db.DeferredLoadingEnabled = true;//this is the default value
product = db.Products.Where(o => o.ProductSubcategoryID != null).FirstOrDefault();
}
//this will raise an error - Cannot access a disposed object.
return product.ProductSubcategory.Name;
通过将DeferredLoadingEnabled
设置为false
可以解决此问题。
逐步构建您的查询
在使用LINQ时,理解何时实际执行查询非常重要。一般规则是,当您尝试访问查询数据时,查询就会被执行。如您在前面的示例中所见,FirstOrDefault()
会导致调用数据库,因为必须读取查询结果并将其分配给Product对象的属性。但是,如果您不访问数据,您可以继续修改查询而无需执行任何数据库调用。这使您有可能为不同的条件构建不同的查询。
using (AdventureWorksDataContext db = new AdventureWorksDataContext())
{
var products = db.Products.Where(o => o.ProductSubcategoryID != null);
switch (caseNumber)
{
case 1:
products = products.OrderBy(o => o.ProductNumber);
break;
case 2:
products = products.Where(o => o.ProductModelID != null);
break;
}
/*
The query will be executed only here and will look like this(for caseNumber = 1):
SELECT TOP (1) [t0].[ProductID], [t0].[Name],
[t0].[ProductNumber], [t0].[MakeFlag], [t0].[FinishedGoodsFlag],
[t0].[Color], [t0].[SafetyStockLevel],
[t0].[ReorderPoint], [t0].[StandardCost],
[t0].[ListPrice], [t0].[Size], [t0].[SizeUnitMeasureCode],
[t0].[WeightUnitMeasureCode], [t0].[Weight],
[t0].[DaysToManufacture], [t0].[ProductLine], [t0].[Class], [t0].[Style],
[t0].[ProductSubcategoryID], [t0].[ProductModelID],
[t0].[SellStartDate], [t0].[SellEndDate], [t0].[DiscontinuedDate],
[t0].[rowguid], [t0].[ModifiedDate]
FROM [Production].[Product] AS [t0]
WHERE [t0].[ProductSubcategoryID] IS NOT NULL
ORDER BY [t0].[ProductNumber]
*/
Product product = products.FirstOrDefault();
return product.ProductNumber;
}
如您所见,对于情况1,生成的查询正如预期的那样。
动态可查询
DynamicQueryable
是一个允许您构建接受字符串参数的查询的类。这可能非常有用,例如,当您需要执行“order by”操作并且以字符串形式获取列名时。
using (AdventureWorksDataContext db = new AdventureWorksDataContext())
{
Product product = db.Products.Where
("ProductSubcategoryID != null").OrderBy("ProductNumber").FirstOrDefault();
return product.ProductNumber;
}
您可以在此解决方案中找到此类,或者从Visual Studio 2008示例页面在此处下载。
LINQ对象和Web应用程序
默认情况下,如果您的应用程序使用进程内状态管理,您将能够将LINQ对象存储在Session、Application和Cache中。但是,您将无法将它们存储在ViewState中。原因是ViewState中存储的数据是使用二进制格式化程序序列化的。为了使LINQ类可序列化,您需要将所有System.Data.Linq.EntitySet
和System.Data.Linq.EntityRef
字段标记为NonSerialized
属性,并将LINQ类标记为Serializable
属性。
另一个解决方案是手动从LINQ对象中提取数据,将其存储到某种中间格式(类、结构体等),然后再手动转换回来。
另一个解决方案是使用Json.NET库。
该库允许您将LINQ对象双向转换为其JSON表示形式。例如,此代码将允许您将产品持久化到ViewState中
Product product = ... get product
ViewState["product"] = JsonConvert.SerializeObject(product);
product = JsonConvert.DeserializeObject<Product>(ViewState["product"] as string);
LINQ对象和Web服务
XML序列化用于从Web服务返回数据。在大多数情况下,从Web服务返回LINQ对象会因为模型中存在的循环引用而失败。基本上,XML序列化对于任何被引用或引用另一个对象的对象都会失败。您可以使用与前面示例相同的解决方法。但是,您可以毫无问题地使用LINQ生成的类作为Web服务输入参数的类型。
Attach方法
在使用LINQ时,您很可能会想将LINQ对象传递给不同的方法和类。问题在于,您不能简单地将在一个数据上下文中创建的LINQ对象用于另一个数据上下文。要能够使用它们,您需要将它们附加到上下文中。这时您应该使用attach方法。该方法还允许您更新在上下文之外已更改的实体。下面是一个演示此功能的示例
ProductCategory changedProductCategory = null;
using (AdventureWorksDataContext db = new AdventureWorksDataContext())
{
db.DeferredLoadingEnabled = false;
changedProductCategory = db.ProductCategories.FirstOrDefault();
}
string json = JsonConvert.SerializeObject(changedProductCategory);
//re-create the object
changedProductCategory = JsonConvert.DeserializeObject<ProductCategory>(json);
//change modified date
changedProductCategory.ModifiedDate = DateTime.Now;
ProductCategory originalProductCategory = null;
//create second context to get original entity
using (AdventureWorksDataContext db = new AdventureWorksDataContext())
{
originalProductCategory = db.ProductCategories.Where(o => o.ProductCategoryID ==
changedProductCategory.ProductCategoryID).FirstOrDefault();
}
//create third context to do the update
using (AdventureWorksDataContext db = new AdventureWorksDataContext())
{
db.ProductCategories.Attach(changedProductCategory, originalProductCategory);
//Here an update query is executed and ModifiedDate is updated in the database.
//Notice that the column that changed is determined automatically.
db.SubmitChanges();
}
请注意,为了使此attach方法正常工作,传递给该方法的所有实体都应在当前数据上下文之外创建。
平移
这是一个非常实用的方法,它允许您将DBDataReader
转换为LINQ对象列表。
IEnumerable<ProductCategory> prodCatLst = db.Translate<ProductCategory>(reader);
在LINQ中使用DATEDIFF和LIKE
如果您需要使用SQL Server的DATEDIFF
函数或LIKE
功能,您只需在LINQ查询中使用System.Data.Linq.SqlClient.SqlMethods
类的相应方法即可。下面是一个使用这些方法的示例
using (AdventureWorksDataContext db = new AdventureWorksDataContext())
{
/*
This will produce the following query:
SELECT [t0].[ProductCategoryID], [t0].[Name],
[t0].[rowguid], [t0].[ModifiedDate]
FROM [Production].[ProductCategory] AS [t0]
WHERE [t0].[Name] LIKE '%ike%'
*/
ProductCategory pc = db.ProductCategories.Where(o =>
SqlMethods.Like(o.Name, "%ike%")).FirstOrDefault();
/*
This will produce the following query:
SELECT TOP (1) [t0].[ProductCategoryID],
[t0].[Name], [t0].[rowguid], [t0].[ModifiedDate]
FROM [Production].[ProductCategory] AS [t0]
WHERE DATEDIFF(Year, [t0].[ModifiedDate], @p0) > 1
Where @p0 is equal to DateTime.Now value we passed
*/
pc = db.ProductCategories.Where(o =>
SqlMethods.DateDiffYear(o.ModifiedDate, DateTime.Now) > 1).FirstOrDefault();
}
结论
感谢您的阅读。 我希望这对您有用。
历史
- 2009年12月5日:首次发布