65.9K
CodeProject 正在变化。 阅读更多。
Home

LINQ to SQL 技巧和窍门

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (22投票s)

2009年12月5日

CPOL

4分钟阅读

viewsIcon

68839

downloadIcon

591

本文介绍了一些 LINQ to SQL 的技巧和窍门。

引言

在本文中,我将介绍一些我在使用LINQ时发现的技巧。

源代码

源代码包含Web应用程序,其中包含本文所述所有技巧的代码示例。要运行该应用程序,您需要AdventureWorks数据库,该数据库可以在此处下载。您还需要在配置文件中更新连接字符串,使其具有正确的值。

为保持示例简单,我只将3个表导入了LINQ模型。下面是模型架构

schema.JPG

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.EntitySetSystem.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日:首次发布
© . All rights reserved.