我们可以使用 Skip()、Take() 和 OrderBy() 控制 LINQ 表达式顺序吗

发布于 2024-08-26 06:58:24 字数 1904 浏览 8 评论 0原文

我正在使用 LINQ to Entities 来显示分页结果。但我在使用 Skip()Take()OrderBy() 调用的组合时遇到问题。

一切工作正常,除了 OrderBy() 分配得太晚。它在结果集被 Skip()Take() 削减后执行。

因此,每页结果都有按顺序排列的项目。但排序是在一页数据上完成的,而不是对整个数据集进行排序,然后使用 Skip()Take() 限制这些记录。

如何设置这些语句的优先级?

我的示例(简化)

var query = ctx.EntitySet.Where(/* filter */).OrderByDescending(e => e.ChangedDate);
int total = query.Count();
var result = query.Skip(n).Take(x).ToList();

一种可能(但不好)的解决方案

一种可能的解决方案是应用聚集索引按列排序,但该列经常更改,这会导致会降低插入和更新时的数据库性能。我真的不想这样做。

编辑

我在查询中运行了 ToTraceString() ,我们可以实际看到何时将 order by 应用于结果集。不幸的是最后。 :(

SELECT 
-- columns
FROM  (SELECT 
    -- columns
    FROM   (SELECT -- columns
        FROM ( SELECT 
            -- columns
            FROM table1 AS Extent1
            WHERE  EXISTS (SELECT 
                -- single constant column
                FROM table2 AS Extent2
                WHERE (Extent1.ID = Extent2.ID) AND (Extent2.userId = :p__linq__4)
            )
        )  AS Project2
        limit 0,10  ) AS Limit1
    LEFT OUTER JOIN  (SELECT 
        -- columns
        FROM table2 AS Extent3 ) AS Project3 ON Limit1.ID = Project3.ID
UNION ALL
    SELECT 
    -- columns
    FROM   (SELECT -- columns
        FROM ( SELECT 
            -- columns
            FROM table1 AS Extent4
            WHERE  EXISTS (SELECT 
                -- single constant column
                FROM table2 AS Extent5
                WHERE (Extent4.ID = Extent5.ID) AND (Extent5.userId = :p__linq__4)
            )
        )  AS Project6
        limit 0,10  ) AS Limit2
    INNER JOIN table3 AS Extent6 ON Limit2.ID = Extent6.ID) AS UnionAll1
ORDER BY UnionAll1.ChangedDate DESC, UnionAll1.ID ASC, UnionAll1.C1 ASC

I'm using LINQ to Entities to display paged results. But I'm having issues with the combination of Skip(), Take() and OrderBy() calls.

Everything works fine, except that OrderBy() is assigned too late. It's executed after result set has been cut down by Skip() and Take().

So each page of results has items in order. But ordering is done on a page handful of data instead of ordering of the whole set and then limiting those records with Skip() and Take().

How do I set precedence with these statements?

My example (simplified)

var query = ctx.EntitySet.Where(/* filter */).OrderByDescending(e => e.ChangedDate);
int total = query.Count();
var result = query.Skip(n).Take(x).ToList();

One possible (but a bad) solution

One possible solution would be to apply clustered index to order by column, but this column changes frequently, which would slow database performance on inserts and updates. And I really don't want to do that.

EDIT

I ran ToTraceString() on my query where we can actually see when order by is applied to the result set. Unfortunately at the end. :(

SELECT 
-- columns
FROM  (SELECT 
    -- columns
    FROM   (SELECT -- columns
        FROM ( SELECT 
            -- columns
            FROM table1 AS Extent1
            WHERE  EXISTS (SELECT 
                -- single constant column
                FROM table2 AS Extent2
                WHERE (Extent1.ID = Extent2.ID) AND (Extent2.userId = :p__linq__4)
            )
        )  AS Project2
        limit 0,10  ) AS Limit1
    LEFT OUTER JOIN  (SELECT 
        -- columns
        FROM table2 AS Extent3 ) AS Project3 ON Limit1.ID = Project3.ID
UNION ALL
    SELECT 
    -- columns
    FROM   (SELECT -- columns
        FROM ( SELECT 
            -- columns
            FROM table1 AS Extent4
            WHERE  EXISTS (SELECT 
                -- single constant column
                FROM table2 AS Extent5
                WHERE (Extent4.ID = Extent5.ID) AND (Extent5.userId = :p__linq__4)
            )
        )  AS Project6
        limit 0,10  ) AS Limit2
    INNER JOIN table3 AS Extent6 ON Limit2.ID = Extent6.ID) AS UnionAll1
ORDER BY UnionAll1.ChangedDate DESC, UnionAll1.ID ASC, UnionAll1.C1 ASC

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

国产ˉ祖宗 2024-09-02 06:58:24

我的解决方案

我已经成功解决了这个问题。不要误会我的意思。到目前为止我还没有解决优先级问题,但我已经减轻了它。

我做了什么?

这是我在从 Devart 得到答案之前一直使用的代码。如果他们无法解决这个问题,我最终将不得不使用此代码。

// get ordered list of IDs
List<int> ids = ctx.MyEntitySet
    .Include(/* Related entity set that is needed in where clause */)
    .Where(/* filter */)
    .OrderByDescending(e => e.ChangedDate)
    .Select(e => e.Id)
    .ToList();

// get total count
int total = ids.Count;

if (total > 0)
{
    // get a single page of results
    List<MyEntity> result = ctx.MyEntitySet
        .Include(/* related entity set (as described above) */)
        .Include(/* additional entity set that's neede in end results */)
        .Where(string.Format("it.Id in {{{0}}}", string.Join(",", ids.ConvertAll(id => id.ToString()).Skip(pageSize * currentPageIndex).Take(pageSize).ToArray())))
        .OrderByDescending(e => e.ChangedOn)
        .ToList();
}

首先,我得到了我的实体的有序 ID。即使对于较大的数据集,仅获取 ID 也具有良好的性能。 MySql 查询非常简单并且性能非常好。在第二部分中,我对这些 ID 进行分区并使用它们来获取实际的实体实例。

考虑到这一点,这应该比我一开始的方式执行得更好(如我的问题中所述),因为由于简化的查询,获得总计数要快得多。第二部分实际上非常相似,只是我的实体是通过 ID 返回的,而不是使用 SkipTake 进行分区...

希望有人会发现这个解决方案有帮助。

My workaround solution

I've managed to workaround this problem. Don't get me wrong here. I haven't solved precedence issue as of yet, but I've mitigated it.

What I did?

This is the code I've used until I get an answer from Devart. If they won't be able to overcome this issue I'll have to use this code in the end.

// get ordered list of IDs
List<int> ids = ctx.MyEntitySet
    .Include(/* Related entity set that is needed in where clause */)
    .Where(/* filter */)
    .OrderByDescending(e => e.ChangedDate)
    .Select(e => e.Id)
    .ToList();

// get total count
int total = ids.Count;

if (total > 0)
{
    // get a single page of results
    List<MyEntity> result = ctx.MyEntitySet
        .Include(/* related entity set (as described above) */)
        .Include(/* additional entity set that's neede in end results */)
        .Where(string.Format("it.Id in {{{0}}}", string.Join(",", ids.ConvertAll(id => id.ToString()).Skip(pageSize * currentPageIndex).Take(pageSize).ToArray())))
        .OrderByDescending(e => e.ChangedOn)
        .ToList();
}

First of all I'm getting ordered IDs of my entities. Getting only IDs is well performant even with larger set of data. MySql query is quite simple and performs really well. In the second part I partition these IDs and use them to get actual entity instances.

Thinking of it, this should perform even better than the way I was doing it at the beginning (as described in my question), because getting total count is much much quicker due to simplified query. The second part is practically very very similar, except that my entities are returned rather by their IDs instead of partitioned using Skip and Take...

Hopefully someone may find this solution helpful.

谎言月老 2024-09-02 06:58:24

我没有直接使用 Linq to Entities,但它应该有一种方法可以在需要时将特定的存储过程挂接到某些位置。 (Linq to SQL 就是这样做的。)如果是这样,您可以将此查询转换为存储过程,准确地执行所需的操作,并且高效地执行。

I haven't worked directly with Linq to Entities, but it should have a way to hook specific stored procedures into certain locations when needed. (Linq to SQL did.) If so, you could turn this query into a stored procedure, doing exacly what is required, and doing it efficiently.

从﹋此江山别 2024-09-02 06:58:24

假设您的评论认为将值保留在列表中是不可接受的:

没有办法按照您的意图完全最小化迭代(正如我也尝试过的那样,生活在希望中)。将迭代次数减少 1 次就好了。是否可以只获取一次计数并缓存/会话它?然后你可以:

int total = ctx.EntitySet.Count;  // Hopefully you can not repeat doing this.
var result = ctx.EntitySet.Where(/* filter */).OrderBy(/* expression */).Skip(n).Take(x).ToList();

希望你能以某种方式缓存计数,或者避免每次都需要它。即使你做不到,这也是你能做的最好的事情了。

Assuming from you comment the persisting the values in a List is not acceptable:

There's no way to completely minimize the iterations, as you intended (and as I would have tried too, living in hope). Cutting the iterations down by one would be nice. Is it possible to just get the Count once and cache/session it? Then you could:

int total = ctx.EntitySet.Count;  // Hopefully you can not repeat doing this.
var result = ctx.EntitySet.Where(/* filter */).OrderBy(/* expression */).Skip(n).Take(x).ToList();

Hopefully you can cache the Count somehow, or avoid needing it every time. Even if you can't, this is the best you can do.

倾其所爱 2024-09-02 06:58:24

您能否创建一个示例来说明问题并将其发送给我们(支持 * devart * com,主题“EF:Skip、Take、OrderBy”)?
希望我们能够帮助您。
您还可以使用我们的论坛联系表

Could you please create a sample illusrating the problem and send it to us (support * devart * com, subject "EF: Skip, Take, OrderBy")?
Hope we will be able to help you.
You can also contact us using our forums or contact form.

欢烬 2024-09-02 06:58:24

您完全确定订单已关闭吗? SQL 是什么样的?

您可以按如下方式重新排序代码并发布输出吗?

// Redefine your queries. 
var query = ctx.EntitySet.Where(/* filter */).OrderBy(e => e.ChangedDate); 
var skipped = query.Skip(n).Take(x);

// let's look at the SQL, shall we?
var querySQL = query.ToTraceString();
var skippedSQL = skipped.ToTraceString();

// actual execution of the queries...
int total = query.Count(); 
var result = skipped.ToList(); 

编辑:

我绝对确定。您可以检查我的“编辑”以查看查询的跟踪结果,并跳过跟踪结果,这在这种情况下是必需的。计数其实并不重要。

是的,我看到了。哇,这真是一个难题。甚至可能是一个彻底的错误。我注意到你没有使用 SQL Server...你使用什么数据库?看起来可能是MySQl。

Are you absolutely certain the ordering is off? What does the SQL look like?

Can you reorder your code as follows and post the output?

// Redefine your queries. 
var query = ctx.EntitySet.Where(/* filter */).OrderBy(e => e.ChangedDate); 
var skipped = query.Skip(n).Take(x);

// let's look at the SQL, shall we?
var querySQL = query.ToTraceString();
var skippedSQL = skipped.ToTraceString();

// actual execution of the queries...
int total = query.Count(); 
var result = skipped.ToList(); 

Edit:

I'm absolutely certain. You can check my "edit" to see trace result of my query with skipped trace result that is imperative in this case. Count is not really important.

Yeah, I see it. Wow, that's a stumper. Might even be an outright bug. I note you're not using SQL Server... what DB are you using? Looks like it might be MySQl.

谈情不如逗狗 2024-09-02 06:58:24

一种方法:

var query = ctx.EntitySet.Where(/* filter */).OrderBy(/* expression */).ToList();
int total = query.Count;
var result = query.Skip(n).Take(x).ToList();

在跳过之前将其转换为列表。请注意,这不是太有效...

One way:

var query = ctx.EntitySet.Where(/* filter */).OrderBy(/* expression */).ToList();
int total = query.Count;
var result = query.Skip(n).Take(x).ToList();

Convert it to a List before skipping. It's not too efficient, mind you...

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文