You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EF Core实现高效Linq TakeDistinct方法获取前n个不同字段对应数据

优化方案1:用JOIN替代IN查询(推荐,兼容性最好性能最高)

你原来的Contains写法会被翻译成IN子查询,数据库大概率会对每一行外层查询重复执行子查询,数据量大的时候性能极差。改用JOIN写法可以让子查询仅执行一次,EF Core 3.1+都支持这种写法的SQL转换:

public static IQueryable<TSource> TakeDistinct<TSource, TKey>(this IQueryable<TSource> source, int takeCount, Expression<Func<TSource, TKey>> keySelector)
{
    var topDistinctKeys = source.Select(keySelector).Distinct().Take(takeCount);
    return source.Join(topDistinctKeys, keySelector, key => key, (entity, _) => entity);
}

生成的SQL示例(以SQL Server为例):

SELECT t.*
FROM YourTable t
INNER JOIN (
    SELECT DISTINCT TOP(@takeCount) Job 
    FROM YourTable
) d ON t.Job = d.Job

只要你对Key字段(示例中的Job)建了索引,这个语句的执行效率会非常高,子查询仅需要扫描索引就能拿到前N个不重复Key,无需全表扫描。


优化方案2:窗口函数实现(EF Core 8+适用,灵活性高)

如果你使用EF Core 8及以上版本,可以用DENSE_RANK窗口函数实现,适合需要自定义Key排序规则的场景(比如要按最新生成的Job取前N个,而非按Job值排序):

public static IQueryable<TSource> TakeDistinct<TSource, TKey>(this IQueryable<TSource> source, int takeCount, Expression<Func<TSource, TKey>> keySelector)
{
    return source
        .Select(e => new 
        {
            Entity = e,
            KeyRank = EF.Functions.DenseRank().Over(orderBy: keySelector)
        })
        .Where(x => x.KeyRank <= takeCount)
        .Select(x => x.Entity);
}

生成的SQL示例:

SELECT * FROM (
    SELECT *,
        DENSE_RANK() OVER (ORDER BY Job) AS rk
    FROM YourTable
) t
WHERE rk <= @takeCount

性能优化建议

  • 必须给作为分组依据的Key字段添加索引,两种方案都可以直接走索引查询,性能提升幅度可达几十上百倍
  • 无需自定义排序规则时优先选JOIN方案,执行计划更简单,大数据量下性能优于窗口函数方案
  • 当你需要自定义Key的排序逻辑(比如按Job的创建时间倒序取前N个),只需要修改窗口函数的orderBy参数即可,灵活性更高

内容的提问来源于stack exchange,提问作者kofifus

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.04 22:06:01