linq 如何在ABP框架中复制SQL的ORDER BY NEWID()?[副本]

ee7vknir  于 2023-05-04  发布在  其他
关注(0)|答案(3)|浏览(219)

此问题已在此处有答案

EF Code First: How to get random rows(4个答案)
Randomize a List(30个答案)
7天前关闭
如何在ABP框架中复制此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);
  • 具体例子:我有一个包含100个问题的数据集,一个返回30个问题的端点,每个问题都有一个潜在答案的集合。我的目标是确保每次用户调用端点时,它都返回一组不同的问题及其潜在的响应,而不管第一个和第二个结果中是否碰巧存在任何问题。

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

  • 在尝试之后,我得到了这个解决方案,似乎问题在于await _asyncExecuter.ToListAsync(query),我不知道为什么。我试过使用var questions = query.OrderBy(x => Guid.NewGuid()).Take(input.MaxResultCount).ToList();
gv8xihay

gv8xihay1#

Tatranskymedved已经回答了你的问题,但要扩展你似乎感到困惑的元素:
使用以下命令只将选定的项目加载到内存中,而不是先将所有内容加载到内存中:

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

现在真实的的问题是你到底想干什么随机地对整个行集合进行排序,然后使用skip和take作为DB查询没有任何意义。当然,这将从随机集合中获取一页数据,但如果您的目标是加载已随机化的数据页,则这可能不会像您期望的那样工作,因为每个查询都会重新随机化集合,因此您可以,并且将在多个分页调用中返回相同的项。需要在分页调用之间保持排序。如果你只想从整个集合中随机选取100个项目,你不需要Skip,只需要使用Take来获得前100个随机项目。
我不知道_asyncExecutor是什么,但我很确定它不是必需的,除非它是一个记录输出或类似的 Package 器,但我怀疑它是为了做一些像 Package 一个同步操作被视为异步操作。(不需要,因为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,并将其从集合中排除:
初始状态:

List<int> questionIdsAsked = (List<int>)Session[nameof(questionIdsAsked)] ?? new List<int>();

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个新问题之前从查询中排除这些问题。显然,如果您采用这种方法,您将需要处理可能会用完问题或足以获得完整的30个问题的场景。
编辑:请注意,如果您急于加载响应或其他数据,则不能将AsSplitQuery与此方法一起使用。这将导致对正在阅读的问题的随机响应集,因此一些问题可能会加载其答案,而其他问题则不会。使用Split Queries,EF会生成两个查询,看起来像这样:

SELECT TOP 10 * FROM Questions
ORDER BY NEWID()

SELECT r.* FROM Responses r
INNER JOIN Questions q ON r.QuestionId = q.Id
WHERE q.Id IN (SELECT TOP 10 Id FROM Questions 
ORDER BY NEWID())

第一个查询将获取10个随机问题,但是为了使拆分查询可靠地工作,排序必须相同,在本例中并非如此。它将加载不同的10个随机问题的响应。
您需要接受问题及其回答的笛卡尔积的成本,或者可以考虑手动执行拆分查询:

// Note: No eager load.
var questions = await _context.Questions
    .OrderBy(x => Guid.NewGuid())
    .Skip(input.SkipCount)
    .Take(input.MaxResultCount)
    .ToListAsync();
var questionIds = questions.Select(x => x.Id).ToList();
var responses = await _context.Responses
    .Where(x => questionIds.Contains(x.QuestionId))
    .ToListAsync();

更好的方法是将问题和所需的响应细节投影到DTO中,以尽可能减少查询笛卡尔,这样就不必求助于IncludeAsSplitQuery

91zkwejq

91zkwejq2#

这样做的问题是,您首先选择项目(总是相同的集合),然后随机排序。
更改Linq调用的顺序,并在开始获取元素之前调用OrderBy()

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

6rqinv9w3#

我无法重现这个问题(也就是说,我从数据库中获得结果)。我使用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

我从数据库中随机得到两行。但是,由于行是随机的,我不确定Skip()的必要性

相关问题