LINQ to SQL Take w / o Skip导致多个SQL语句

时间:2009-05-13 13:00:23

标签: sql-server linq linq-to-sql

我有一个LINQ to SQL查询:

from at in Context.Transaction
select new  {
    at.Amount,
    at.PostingDate,
    Details = 
        from tb in at.TransactionDetail
        select new {
            Amount = tb.Amount,
            Description = tb.Desc
        }
}

这导致一个SQL语句被执行。一切都很好。

但是,如果我尝试从此查询返回已知类型,即使它们具有与匿名类型相同的结构,我会为顶级执行一个SQL语句,然后为每个“子”集创建一个额外的SQL语句

有没有办法让LINQ to SQL发出一个SQL语句并使用已知类型?

编辑:我必须有另一个问题。当我将一个非常简单(但仍然是高阶段)的查询版本插入LINQPad并使用仅有2或3个成员的新创建的已知类型时,我确实得到了一个SQL语句。当我知道更多时,我会发布和更新。

编辑2:这似乎是由于Take中的一个错误。有关详细信息,请参阅下面的答案。

3 个答案:

答案 0 :(得分:11)

首先 - Take bug的一些推理。

如果您只需,查询翻译器就会使用top。如果通过加入子集合来破坏基数,Top10将无法​​给出正确的答案。因此查询转换器不会加入子集合(而是重新查询子集合)。

如果你跳过并接受,那么查询翻译器会在父行上使用一些RowNumber逻辑...这些rownumber会让它占用10个父项,即使这个记录真的有50个记录父母有5个孩子。

如果您跳过(0)并执行,则翻译会将删除作为非操作删除 - 就像您从未说过Skip一样。

这将是一个艰难的概念上的飞跃,从你所处(称为Skip and Take)到“简单的解决方法”。我们需要做的是 - 强制翻译发生在翻译者无法将Skip(0)作为非操作删除的位置。我们需要调用Skip,并在稍后提供跳过的数字。

DataClasses1DataContext myDC = new DataClasses1DataContext();
  //setting up log so we can see what's going on
myDC.Log = Console.Out;

  //hierarchical query - not important
var query = myDC.Options.Select(option => new{
  ID = option.ParentID,
  Others = myDC.Options.Select(option2 => new{
    ID = option2.ParentID
  })
});
  //request translation of the query!  Important!
var compQuery = System.Data.Linq.CompiledQuery
  .Compile<DataClasses1DataContext, int, int, System.Collections.IEnumerable>
  ( (dc, skip, take) => query.Skip(skip).Take(take) );

  //now run the query and specify that 0 rows are to be skipped.
compQuery.Invoke(myDC, 0, 10);

这会产生以下查询:

SELECT [t1].[ParentID], [t2].[ParentID] AS [ParentID2], (
    SELECT COUNT(*)
    FROM [dbo].[Option] AS [t3]
    ) AS [value]
FROM (
    SELECT ROW_NUMBER() OVER (ORDER BY [t0].[ID]) AS [ROW_NUMBER], [t0].[ParentID]
    FROM [dbo].[Option] AS [t0]
    ) AS [t1]
LEFT OUTER JOIN [dbo].[Option] AS [t2] ON 1=1 
WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p1 + @p2
ORDER BY [t1].[ROW_NUMBER], [t2].[ID]
-- @p0: Input Int (Size = 0; Prec = 0; Scale = 0) [0]
-- @p1: Input Int (Size = 0; Prec = 0; Scale = 0) [0]
-- @p2: Input Int (Size = 0; Prec = 0; Scale = 0) [10]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.30729.1

这就是我们赢的地方!

WHERE [t1].[ROW_NUMBER] BETWEEN @p0 + 1 AND @p1 + @p2

答案 1 :(得分:2)

我现在已经确定这是一个可怕的错误的结果。事实证明,匿名与已知类型不是原因。真正的原因是Take。

1个SQL语句中的以下结果:

query.Skip(1).Take(10).ToList();
query.ToList();

但是,下面展示了每个父行问题的一个sql语句。

query.Skip(0).Take(10).ToList();
query.Take(10).ToList();

有人能想到任何简单的解决方法吗?

编辑:我提出的唯一解决方法是检查我是否在第一页(IE跳过(0)),然后进行两次调用,一次使用Take(1),另一次使用跳过(1)。取(pageSize - 1)并将列表添加到一起。

答案 2 :(得分:0)

我没有机会尝试这个,但鉴于匿名类型不是LINQ而不是C#构造的一部分,我想知道你是否可以使用:

from at in Context.Transaction
select new KnownType(
    at.Amount,
    at.PostingDate,
    Details = 
        from tb in at.TransactionDetail
        select KnownSubType(
                Amount = tb.Amount,
                Description = tb.Desc
        )
}

显然,细节需要是一个IEnumerable集合。

我可以在这里做几英里,但它至少可以给你一个新的思路来追求不受伤害,所以请原谅我的漫无边际。