如何复制 SQL 的 ORDER BY NEWID()?

问题描述 投票:0回答:3

如何使用 Entity Framework 复制此 SQL 查询的行为? SQL查询是:

SELECT * FROM App_Profile
ORDER BY NEWID()

我尝试使用以下 LINQ 表达式,但它不起作用 - 我想每次都获得随机记录,但我得到的结果总是相同的:

query.Skip(input.SkipCount).Take(input.MaxResultCount).OrderBy(x => Guid.NewGuid());
var profiles= await _asyncExecuter.ToListAsync(query);

P.S : 我正在使用 ABP 框架,EF Core 6

  • 尝试后我找到了这个解决方案
query.Skip(input.SkipCount).Take(input.MaxResultCount);
var questionsWithResponses = (await _asyncExecuter.ToListAsync(query)).OrderBy(x => Guid.NewGuid()).ToList();
  • 但问题是我不想加载数据然后改变它们的顺序我想从数据库中得到一个现成的结果

  • 具体示例:我有一个包含 100 个问题的数据集和一个返回 30 个问题的端点。我想做的是确保每次用户调用端点时,它都会返回一组不同的问题,而不管第一个和第二个结果中是否恰好存在任何问题。

c# linq entity-framework-core ef-core-6.0
3个回答
1
投票

问题是,您首先选择项目(总是同一组),然后随机排序。

更改 Linq 调用的顺序并在开始获取元素之前调用

OrderBy()

query.OrderBy(x => Guid.NewGuid())
     .Skip(input.SkipCount)
     .Take(input.MaxResultCount);

0
投票

我无法重现问题(即,我从数据库中获取结果)。我正在使用 EF Core 7 和 SQL Server。具体来说,我有以下代码:

IQueryable<Agency> agencies = _db.Agency.OrderBy(a => Guid.NewGuid()).Skip(3).Take(2); 
Console.WriteLine(agencies.ToQueryString());
foreach (Agency agency in agencies.ToList())
{
    Console.WriteLine($"{agency.AgencyId} - {agency.AgencyName}");
}

我看到生成了以下查询:

DECLARE @__p_0 int = 3;
DECLARE @__p_1 int = 2;

SELECT [a].[agencyId], [a].[agencyName]
FROM [Agency] AS [a]
ORDER BY NEWID()
OFFSET @__p_0 ROWS FETCH NEXT @__p_1 ROWS ONLY

我从数据库中随机得到 2 行。虽然,由于行是随机的,我不确定需要什么

Skip()


0
投票

Tatranskymedved 已经回答了您的问题,但要扩展您似乎感到困惑的元素:

使用以下仅加载所选项目,而不是首先将所有内容加载到内存中:

var profiles = await query.OrderBy(x => Guid.NewGuid())
    .Skip(input.SkipCount)
    .Take(input.MaxResultCount)
    .ToListAsync();

现在真正的问题是你想在这里完成什么?对整组行进行随机排序,然后使用 skip and take as DB 查询没有任何意义。当然,这将从随机集中获取一页数据,但如果您的目标是加载已随机化的数据页,这将不会像您预期的那样工作,因为每个查询都会重新随机化该集,因此您可以, 并将在多个分页调用中取回相同的项目。需要在寻呼呼叫之间保持排序。如果您只想从整个集合中随机抽取 100 个物品,则不需要

Skip
,只需使用
Take
即可获得前 100 个随机物品。

我不知道 _asyncExecutor 是什么,但我很确定它不是必需的,除非它是记录输出等的包装器,但我怀疑它是为了做一些像包装同步操作这样的事情被视为异步的。 (不需要,因为 EF 支持异步操作)

用你的例子解释你所看到的:

query.Skip(input.SkipCount)
    .Take(input.MaxResultCount)
    .OrderBy(x => Guid.NewGuid());

var profiles= await query.ToListAsync();

给定 Skip 值为 100 和 MaxResultCount 为 10 那么这将始终采用 101-110 行然后随机排序这 10 个结果而不是随机排序整个集合。这种方法的另一个问题是假定的默认数据读取顺序不可靠,并且会随着数据从集合中添加/删除而变化。它首先看起来默认顺序将是添加行的 ID 或顺序,但随着集合的增长和变化,这将不会重复可靠。

具体例子:我有一个100个问题的数据集和一个端点 返回 30 个问题。我想做的是确保每个 用户调用端点时,它将返回一组不同的 问题,不管是否有任何问题发生 存在于第一个和第二个结果中。

如果您想从 100 个问题的数据集中随机选择 30 个问题,并且您不关心是否可以在调用之间重复问题:

var profiles = await query.OrderBy(x => Guid.NewGuid())
    .Take(input.MaxResultCount)
    .ToListAsync();

这就是你所需要的。

如果你想确保接下来的30个问题不能包含用户已经尝试过的问题,那么确保这一点的最好方法是缓存你已经选择的问题ID,并将其从集合中排除:

初始状态: 列出 questionIdsAsked = (List)Session[nameof(questionIdsAsked)] ??新列表();

if(questionIdsAsked.Any())
    query = query.Where(x => !questionIdsAsked.Contains(x.Id));

var questions = await query.OrderBy(x => Guid.NewGuid())
    .Take(input.MaxResultCount)
    .ToListAsync();

questionIdsAsked.AddRange(questions.Select(x => x.Id));
Session[nameof(questionIdsAsked)] = questionIdsAsked;

假设是一个 Web 应用程序,但如果是一个应用程序,则 questionIdsAsked 可以只是一个私有成员,必要时可以将其清除。这将检查是否为当前用户提供了一组问题。在会话的第一次运行中,我们从数据库中获取前 30 个问题并将这些问题 ID 记录到会话状态中。这样,当我们再次调用它时,我们会从上一次运行中获得 30 个问题 ID,并在重新随机化并采用 30 个新问题之前从查询中排除这些 ID。显然,如果采用这种方法,您将需要处理可能用完问题或足以获得全套 30 个问题的场景。

© www.soinside.com 2019 - 2024. All rights reserved.