Linq to SQL - 加入和跳过+ Take

时间:2012-02-28 02:13:39

标签: c# sql linq linq-to-sql

我有使用多对多关系的Linq-to-SQL代码,但请注意,关系本身有自己的一组属性(在这种情况下,产品属于多个类别,每个产品都有-category关系有自己的SortOrder属性。)

我有一个Linq-to-SQL块,它返回带有类别成员资格信息的匹配产品。当我执行代码时,它生成优化的T-SQL代码,如下所示:

exec sp_executesql N'SELECT [t0].[ProductId], [t0].[Name], [t1].[ProductId] AS [ProductId2], [t1].[CategoryId], [t1].[SortOrder] AS [SortOrder2], [t2].[CategoryId] AS [CategoryId2], [t2].[Name] AS [Name2] (
SELECT COUNT(*)
FROM [dbo].[ProductsInCategories] AS [t3]
INNER JOIN [dbo].[Categories] AS [t4] ON [t4].[CategoryId] = [t3].[CategoryId]
WHERE [t3].[ProductId] = [t0].[ProductId]
) AS [value]
FROM [dbo].[Products] AS [t0]
LEFT OUTER JOIN ([dbo].[ProductsInCategories] AS [t1]
INNER JOIN [dbo].[Categories] AS [t2] ON [t2].[CategoryId] = [t1].[CategoryId]) ON [t1].[ProductId] = [t0].[ProductId]
WHERE (([t0].[OwnerId]) = @p0) AND ([t0].[Visible] = 1)
ORDER BY [t0].[SortOrder], [t0].[Name], [t0].[ProductId], [t1].[CategoryId]',N'@p0 bigint',@p0=3

但是,当我向Linq表达式添加分页指令(即“.Skip(0).Take(50)”)时,生成的SQL变为:

exec sp_executesql N'SELECT TOP (50) [t0].[ProductId], [t0].[Name]
FROM [dbo].[Products] AS [t0]
WHERE (([t0].[OwnerId]) = @p0) AND ([t0].[Visible] = 1)
ORDER BY [t0].[SortOrder], [t0].[Name]',N'@p0 bigint',@p0=3

这意味着不再加载类别成员资格信息,因此Linq-to-SQL然后执行50次手动加载代码(对于返回集合中的每个成员一次):

exec sp_executesql N'SELECT [t0].[ProductId], [t0].[CategoryId], [t0].[SortOrder], [t1].[CategoryId] AS [CategoryId2], [t1].[Name]
FROM [dbo].[ProductsInCategories] AS [t0]
INNER JOIN [dbo].[Categories] AS [t1] ON [t1].[CategoryId] = [t0].[CategoryId]
WHERE [t0].[ProductId] = @x1',N'@x1 bigint',@x1=1141

(显然,“@ x1”ID参数因原始查询的每个结果而异。)

很明显,Linq分页会破坏查询并导致它分别加载数据。有没有办法解决这个问题,还是应该在我自己的软件中进行分页?

...幸运的是,数据库中的产品数量足够小(<500)来做到这一点,但它只是感觉很脏,因为可能成千上万的产品,而这这不是一个好的问题。

编辑:

这是我的Linq:

DataLoadOptions dlo = new DataLoadOptions();
dlo.LoadWith<Product>( p => p.ProductsInCategories );
dlo.LoadWith<ProductsInCategory>( pic => pic.Category );
this.LoadOptions = dlo;

query = from p in this.Products
select p;

// The lines below are added conditionally:
query = query.OrderBy( p => p.SortOrder ).ThenBy( p => p.Name );
query = query.Where( p => p.Visible );
query = query.Where( p => p.Name.Contains( filter ) || p.Description.Contains( filter ) );
query = query.Where( p => p.OwnerId == siteId );

可选择添加skip / take行,这是导致生成不同T-SQL的唯一差异(据我所知):

IQueryable<Product> query = GetProducts( siteId, category, filter, showHidden, sortBySortOrder );

///////////////////////////////////

total = query.Count();

var pagedProducts = query.Skip( pageIndex * pageSize ).Take( pageSize );
return pagedProducts;

2 个答案:

答案 0 :(得分:1)

首先对产品进行分页,然后在父子结构中选择产品和类别的替代答案如下:

var filter = "a";
var pageSize = 2;
var pageIndex = 1;

// get the correct products
var query = Products.AsQueryable();

query = query.Where (q => q.Name.Contains(filter));
query = query.OrderBy (q => q.SortOrder).ThenBy(q => q.Name);

// do paging
query = query.Skip(pageSize*pageIndex).Take(pageSize);

// now get products + categories as tree structure
var query2 = query.Select(
    q=>new 
    {
        q.Name, 
        Categories=q.ProductsInCategories.Select (pic => pic.Category)
    });

生成单个SQL语句

-- Region Parameters
DECLARE @p0 NVarChar(1000) = '%a%'
DECLARE @p1 Int = 2
DECLARE @p2 Int = 2
-- EndRegion
SELECT [t2].[Name], [t4].[CategoryId], [t4].[Name] AS [Name2], [t4].[Visible], (
    SELECT COUNT(*)
    FROM (
        SELECT [t5].[CategoryId]
        FROM [ProductsInCategories] AS [t5]
        WHERE [t5].[ProductId] = [t2].[ProductId]
        ) AS [t6]
    INNER JOIN [Categories] AS [t7] ON [t7].[CategoryId] = [t6].[CategoryId]
    ) AS [value]
FROM (
    SELECT [t1].[ProductId], [t1].[Name], [t1].[ROW_NUMBER]
    FROM (
        SELECT ROW_NUMBER() OVER (ORDER BY [t0].[SortOrder], [t0].[Name], [t0].[ProductId]) AS [ROW_NUMBER], [t0].[ProductId], [t0].[Name]
        FROM [Products] AS [t0]
        WHERE [t0].[Name] LIKE @p0
        ) AS [t1]
    WHERE [t1].[ROW_NUMBER] BETWEEN @p1 + 1 AND @p1 + @p2
    ) AS [t2]
LEFT OUTER JOIN ([ProductsInCategories] AS [t3]
    INNER JOIN [Categories] AS [t4] ON [t4].[CategoryId] = [t3].[CategoryId]) ON [t3].[ProductId] = [t2].[ProductId]
ORDER BY [t2].[ROW_NUMBER], [t3].[CategoryId], [t3].[ProductId]

答案 1 :(得分:0)

以下是一种解决方法:您应该根据所有条件构建查询,在那里执行排序,但只选择Product表上的主键(假设这是ProductId列)。 / p>

下一步是采取总计数(计算行应该被跳过并采取), 最后一步是从Product表格中选择ProductId表格query {注释:SkipTake扩展方法中的所有记录应用于query,而不是新的选择本身。)

这将为您提供与您的SELECT类似的声明(来自第一个示例)和相关实体。

修改: 刚刚创建了一个类似的DB结构(根据问题中的原始SQL):

DB structure

然后使用:

using (var db = new TestDataContext())
{
    DataLoadOptions options = new DataLoadOptions();
    options.LoadWith<Product>(p => p.ProductsInCategories);
    options.LoadWith<ProductsInCategory>(pic => pic.Category);
    db.LoadOptions = options;

    var filter = "product";
    var pageIndex = 1;
    var pageSize = 10;

    var query = db.Products
        .OrderBy(p => p.SortOrder)
        .ThenBy(p => p.Name)
        .Where(p => p.Name.Contains(filter) || p.Description.Contains(filter))
        .Select(p => p.ProductId);

    var total = query.Count();

    var products = db.Products
        .Where(p => query.Skip(pageIndex * pageSize).Take(pageSize).Contains(p.ProductId))
        .ToList();
}

.ToList()来电后,products变量保留带有类别的产品类别的产品。这也产生了2个SQL语句,一个 - 用于.Count()语句:

exec sp_executesql N'SELECT COUNT(*) AS [value]
FROM [dbo].[Products] AS [t0]
WHERE ([t0].[Name] LIKE @p0) OR ([t0].[Description] LIKE @p1)',N'@p0 nvarchar(4000),@p1 nvarchar(4000)',@p0=N'%product%',@p1=N'%product%'

另一个.ToList()

exec sp_executesql N'SELECT [t0].[ProductId], [t0].[Name], [t0].[Description], [t0].[SortOrder], [t1].[ProductId] AS [ProductId2], [t1].[CategoryId], [t1].[SortOrder] AS [SortOrder2], [t2].[CategoryId] AS [CategoryId2], [t2].[Name] AS [Name2], (
    SELECT COUNT(*)
    FROM (
        SELECT NULL AS [EMPTY]
        FROM [dbo].[ProductsInCategories] AS [t6]
        INNER JOIN [dbo].[Category] AS [t7] ON [t7].[CategoryId] = [t6].[CategoryId]
        WHERE [t6].[ProductId] = [t0].[ProductId]
        ) AS [t8]
    ) AS [value]
FROM [dbo].[Products] AS [t0]
LEFT OUTER JOIN ([dbo].[ProductsInCategories] AS [t1]
    INNER JOIN [dbo].[Category] AS [t2] ON [t2].[CategoryId] = [t1].[CategoryId]) ON [t1].[ProductId] = [t0].[ProductId]
WHERE EXISTS(
    SELECT NULL AS [EMPTY]
    FROM (
        SELECT [t4].[ProductId]
        FROM (
            SELECT ROW_NUMBER() OVER (ORDER BY [t3].[SortOrder], [t3].[Name], [t3].[ProductId]) AS [ROW_NUMBER], [t3].[ProductId]
            FROM [dbo].[Products] AS [t3]
            WHERE ([t3].[Name] LIKE @p0) OR ([t3].[Description] LIKE @p1)
            ) AS [t4]
        WHERE [t4].[ROW_NUMBER] BETWEEN @p2 + 1 AND @p2 + @p3
        ) AS [t5]
    WHERE [t5].[ProductId] = [t0].[ProductId]
    )
ORDER BY [t0].[ProductId], [t1].[CategoryId]',N'@p0 nvarchar(4000),@p1 nvarchar(4000),@p2 int,@p3 int',@p0=N'%product%',@p1=N'%product%',@p2=10,@p3=10

不再需要额外的查询(正如SQL Server Profiler所说)。