【问题标题】:LINQ to SQL Take w/o Skip Causes Multiple SQL StatementsLINQ to SQL Take w/o Skip 导致多个 SQL 语句
【发布时间】:2009-05-13 13:00:22
【问题描述】:

我有一个 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 中的一个错误。有关详细信息,请参阅下面的答案。

【问题讨论】:

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


    【解决方案1】:

    首先 - Take 错误的一些推理。

    如果您只是采取,则查询翻译器只使用顶部。如果加入子集合破坏了基数,Top10 将不会给出正确答案。所以查询翻译器不会加入子集合(而是为子集合请求)。

    如果您Skip and Take,那么查询翻译器会在父行上使用一些 RowNumber 逻辑...这些行号让它占用 10 个父行,即使每个父行确实有 50 条记录父母有 5 个孩子。

    如果您Skip(0) 和Take,则Skip 会被翻译器作为非操作删除-就像您从未说过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
    

    【讨论】:

    • 这行得通,但我必须使用 IEnumerable 而不是 IEnumerable 才能调用 ToList。在我的测试用例中,它也快了半秒。很好的答案。
    【解决方案2】:

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

    以下结果为 1 条 SQL 语句:

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

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

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

    谁能想到任何简单的解决方法?

    编辑:我想出的唯一解决方法是检查我是否在第一页(IE Skip(0)),然后拨打两个电话,一个使用 Take(1),另一个使用Skip(1).Take(pageSize - 1) 和 addRange 将列表放在一起。

    【讨论】:

    • 这里的问题不只是您使用第一个ToList() 调用SQL 执行,然后第二个ToList() 调用对现在在内存中的第一个结果的每个项目的查询吗?
    【解决方案3】:

    我没有机会尝试这个,但鉴于匿名类型不是 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
            )
    }
    

    显然 Details 需要是一个 IEnumerable 集合。

    我可能在这方面很宽泛,但它至少可能会给你一个新的思路去追求它不会伤害所以请原谅我的漫无边际。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-10-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多