来自 SQL 的 LINQ-TO-Entities

LINQ-TO-Entities from SQL

提问人:Kumar 提问时间:11/9/2023 最后编辑:Kumar 更新时间:11/13/2023 访问量:84

问:

我需要一些帮助将下面的 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);

我可以如上所示重写,但我不想使用客户端评估来避免内存问题

C# LINQ-to-entities EF-CORE-7.0

评论

0赞 jcx200 11/9/2023
请注意,如果要最大程度地减少对内存的影响,可以使用 AsEnumerable() 或 AsAsyncEnumerable(需要使用 await foreach/yield return 添加额外的处理),而不是 ToListAsync。除非需要多次枚举,否则我会这样做。
0赞 Kumar 11/10/2023
我认为这行不通,因为我在 ToListAsync() 之后有一个 OrderByDescending。思潮?

答:

0赞 jcx200 11/13/2023 #1

根据我提供的评论,我检查了一些可以实现的方法。我按照与所提供信息类似的结构生成了一些示例数据。

如果你想要缩短它,如果你打算对返回的数据执行进一步的操作,你可以使用 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 运行这些,将获得以下结果:

BenchmarkDotNet Results

我不应该,这是假设对集合没有其他操作,它只是按原样返回。

如果开始对返回的数据执行操作,但 Enumerables 尚未具体化,则最终可能会导致更多性能问题。

使用 Enumerables,随着 N 个操作数的增加,分配的内存和执行时间将增加,而 List 操作则保持相当一致。

One operation on each collection type Two operations on each collection type Three operations on each collection type