提问人:Kumar 提问时间:11/9/2023 最后编辑:Kumar 更新时间:11/13/2023 访问量:84
来自 SQL 的 LINQ-TO-Entities
LINQ-TO-Entities from SQL
问:
我需要一些帮助将下面的 SQL 转换为 LINQ
SELECT a.Id FROM
(
SELECT
s.student_id AS Id,
COALESCE(l.city_name, '') AS City,
ROW_NUMBER() OVER (
PARTITION BY s.student_id
ORDER BY COALESCE(l.city_name, '') DESC)
AS RowNumber
FROM student s
JOIN student_location sl
ON s.student_id = sl.student_id
LEFT JOIN location l
ON l.location_id = sl.location_id
WHERE s.is_active
) a
WHERE a.RowNumber = 1
ORDER BY a.City DESC
LIMIT 500
OFFSET 5000;
到目前为止,我已经尝试像下面这样编写它,其中我使用模型导航属性而不是使用 GroupJoin/SelectMany,但下面的 LINQ 无法按行按外顺序翻译
.OrderByDescending(x => x.Location.CityName)
var students = await context.Set<StudentLocationEntity>().AsNoTracking()
.Join(context.Set<StudentEntity>().AsNoTracking().Where(x => x.IsActive),
a => a.StudentId,
b => b.StudentId,
(a, b) => a)
.Include(e => e.LocationEntity)
.GroupBy(e => e.StudentId)
.Select(x => x.OrderByDescending(y => y.Location.CityName).ThenBy(z => z.StudentId).FirstOrDefault())
.OrderByDescending(x => x.Location.CityName)
.Skip(5000)
.Take(500)
.Select(x => x.StudentId)
.ToListAsync();
错误消息:System.InvalidOperationException:无法转换 LINQ 表达式。以可翻译的形式重写查询,或者通过插入对“AsEnumerable”、“AsAsyncEnumerable”、“ToList”或“ToListAsync”的调用来显式切换到客户端评估。有关详细信息,请参阅 https://go.microsoft.com/fwlink/?linkid=2101038
var data = await context.Set<StudentLocationEntity>().AsNoTracking()
.Join(context.Set<StudentEntity>().AsNoTracking().Where(x => x.IsActive),
a => a.StudentId,
b => b.StudentId,
(a, b) => a)
.Include(e => e.LocationEntity)
.GroupBy(e => e.StudentId)
.Select(x => x.OrderByDescending(y => y.Location.CityName).ThenBy(z => z.StudentId).FirstOrDefault())
.ToListAsync();
var students = data
.OrderByDescending(x => x.Location.CityName)
.Skip(5000)
.Take(500)
.Select(x => x.StudentId);
我可以如上所示重写,但我不想使用客户端评估来避免内存问题
答:
根据我提供的评论,我检查了一些可以实现的方法。我按照与所提供信息类似的结构生成了一些示例数据。
如果你想要缩短它,如果你打算对返回的数据执行进一步的操作,你可以使用 ToListAsync() 。如果您只想按原样返回数据,那么也许使用 Enumerable 强制转换
首先,如果你真的想使用这个选项并将其组合成一个变量,而不是你提供的变量,你可以这样做。就我个人而言,我认为它更干净、更容易以你的方式阅读,但这真的取决于你。ToListAsync()
public async Task CastToListAsync()
{
var data = (await _db.StudentLocation.AsNoTracking()
.Join(_db.Student.AsNoTracking().Where(x => x.IsActive),
a => a.StudentID,
b => b.ID,
(a, b) => a
)
.Include(e => e.Location)
.GroupBy(e => e.StudentID)
.Select(x => x
.OrderByDescending(y => y.Location.CityName)
.ThenBy(z => z.StudentID)
.FirstOrDefault()
)
.ToListAsync())
.OrderByDescending(x => x.Location.CityName)
.Skip(5000)
.Take(500)
.Select(x => x.StudentID);
}
关于可以使用 Enumerable/AsyncEnumerable 的内存问题,可以使用以下方法之一。
public void CastAsEnumerable()
{
var data = _db.StudentLocation.AsNoTracking()
.Join(_db.Student.AsNoTracking().Where(x => x.IsActive),
a => a.StudentID,
b => b.ID,
(a, b) => a
)
.Include(e => e.Location)
.GroupBy(e => e.StudentID)
.Select(x => x
.OrderByDescending(y => y.Location.CityName)
.ThenBy(z => z.StudentID)
.FirstOrDefault()
)
.AsEnumerable()
.OrderByDescending(x => x.Location.CityName)
.Skip(5000)
.Take(500)
.Select(x => x.StudentID);
}
// AsyncEnumerable
public void CastToAsyncEnumerable()
{
var data = BuildQuery().ToBlockingEnumerable()
.OrderByDescending(x => x.Location.CityName)
.Skip(5000)
.Take(500)
.Select(x => x.StudentID);
}
private async IAsyncEnumerable<StudentLocation?> BuildQuery()
{
var query = _db.StudentLocation.AsNoTracking()
.Join(_db.Student.AsNoTracking().Where(x => x.IsActive),
a => a.StudentID,
b => b.ID,
(a, b) => a
)
.Include(e => e.Location)
.GroupBy(e => e.StudentID)
.Select(x => x
.OrderByDescending(y => y.Location.CityName)
.ThenBy(z => z.StudentID)
.FirstOrDefault()
)
.AsAsyncEnumerable();
await foreach (var data in query)
{
yield return data;
}
}
(注意:我不完全确定这是否是使用 AsyncEnumerable 的最佳方式,因为我没有太多经验)
通过 BenchmarkDotNet 运行这些,将获得以下结果:
我不应该,这是假设对集合没有其他操作,它只是按原样返回。
如果开始对返回的数据执行操作,但 Enumerables 尚未具体化,则最终可能会导致更多性能问题。
使用 Enumerables,随着 N 个操作数的增加,分配的内存和执行时间将增加,而 List 操作则保持相当一致。
下一个:EF 生成低效查询
评论