Linq 采取不同的方式
Posted
技术标签:
【中文标题】Linq 采取不同的方式【英文标题】:Linq take distinct 【发布时间】:2021-11-04 20:08:37 【问题描述】:我正在尝试实现 Linq to SQL 运算符(EF 核心)
q.TakeDistinct(n, src => src.Field)
所以它返回来自 q 的前 n 个元素,只计算不同的 Field
基本上我想要一个Take
,它不会计算字段中没有差异的元素
例如:
Job Status
10 start
10 progress
10 alert
10 done
12 start
12 error
12 done
32 start
32 info
32 done
然后
ctx.Table.TakeDistinct(2, v => v.Job)
将返回:
10 start
10 progress
10 alert
10 done
12 start
12 error
12 done
我尝试了 Take
和 Distinct
的各种组合,但没有运气......任何线索都值得赞赏。
注意我正在寻找可以通过 Linq-to-SQL 转换为合理高效 SQL 的东西(不在内存处理中)
编辑:
这行得通:
ctx.Table.Where(z =>
Table.Select(x => x.Field).Distinct().Take(n)
.Contains(z.Field)
);
如果 真的 很慢(在 LinqPad 中).. 有更好的方法吗?
【问题讨论】:
@Llama - 我强烈怀疑 OP 正在寻找与***.com/questions/489258/… 等效的 SQL(按属性区分对象)。 @LlamaDistinct
实际上不会给出一个对象......你会想要来自 MoreLinq 的 DistinctBy (或类似的东西)。
IQueryable 或 IEnumerable 需要它?
@SvyatoslavDanyliv IQueryable
【参考方案1】:
以下扩展生成表达式树,它使Distinct().Take(x)
生成并连接到原始查询。它还接受 x => new x.Id, x.Id2
参数作为不同的键。
public static class QueryableExtensions
public static IQueryable<T> TakeDistinct<T, TKey>(this IQueryable<T> source, int take,
Expression<Func<T, TKey>> distinctBy)
var distinctQuery = source.Select(distinctBy).Distinct().Take(take);
var distinctParam = Expression.Parameter(typeof(TKey), "d");
var entityParam = distinctBy.Parameters[0];
var mapping = MapMembers(distinctBy.Body, distinctParam).ToList();
var whereExpr = mapping.Select(t => Expression.Equal(t.Item1, t.Item2))
.Aggregate(Expression.AndAlso);
var whereLambda = Expression.Lambda(whereExpr, entityParam);
var selectManySelector =
Expression.Lambda(
Expression.Convert(
Expression.Call(typeof(Queryable), nameof(Queryable.Where), new[] typeof(T) ,
source.Expression,
whereLambda),
typeof(IEnumerable<T>)
),
distinctParam
);
var selectManyQuery = Expression.Call(typeof(Queryable), nameof(Queryable.SelectMany),
new[] typeof(TKey), typeof(T) , distinctQuery.Expression, selectManySelector);
return source.Provider.CreateQuery<T>(selectManyQuery);
private static IEnumerable<Tuple<Expression, Expression>> MapMembers(Expression expr, Expression projectionPath)
switch (expr.NodeType)
case ExpressionType.New:
var ne = (NewExpression)expr;
for (int i = 0; i < ne.Arguments.Count; i++)
foreach (var e in MapMembers(ne.Arguments[i], Expression.MakeMemberAccess(projectionPath, ne.Members[i])))
yield return e;
break;
default:
yield return Tuple.Create(projectionPath, expr);
break;
【讨论】:
谢谢!我看不出 EF 如何将其转换为 SQL 什么意思?它应该转换为 SQL。或者您需要说明如何启用日志记录? 坦克很多。有没有办法提供用例和为 EF 核心创建的 SQL?我不相信我可以将其粘贴到 LinqPad 中? 为什么不呢? LINQPad 只执行 C# 编译器,然后执行您的代码。【参考方案2】:我理解你的想法。我写了这个扩展方法来实现。 但它需要优化,因为滥用 LinQ 可能会影响性能。
public static IEnumerable<T> TakeDistinct<T, TProperty>(this IEnumerable<T> source, int take, Expression<Func<T, TProperty>> distinctBy)
var orderList = source.AsQueryable().GroupBy(distinctBy).OrderBy(x=>x.Key).Take(take).Select(x=>x.Key).ToList();
var mappingFunc = distinctBy.Compile();
return source.Where(x => orderList.Contains(mappingFunc.Invoke(x)));
static void Main()
var jobLines = @"10 start
10 progress
10 alert
10 done
12 start
12 error
12 done
32 start
32 info
32 done";
var jobs = jobLines.Split('\n').Select(line =>
var splited = line.Split(new[] " " , StringSplitOptions.RemoveEmptyEntries);
return new JobItem
Job = Convert.ToInt32(splited[0]),
Status = splited[1]
;
);
var newJobs = jobs.TakeDistinct(2, x => x.Job);
foreach (var job in newJobs)
Console.WriteLine($"job.Job - job.Status");
Console.ReadLine();
class JobItem
public int Job get; set;
public string Status get; set;
public static IEnumerable<T> TakeDistinct<T, TProperty>(this IEnumerable<T> source, int take, Expression<Func<T, TProperty>> distinctBy)
var orderList = source.AsQueryable().GroupBy(distinctBy).OrderBy(x=>x.Key).Take(take).Select(x=>x.Key).ToList();
var mappingFunc = distinctBy.Compile();
return source.Where(x => orderList.Contains(mappingFunc.Invoke(x)));
--- 已更新----
本次更新是为了支持 LinQ to SQL
public static IQueryable<T> TakeDistinct<T, TProperty>(this IQueryable<T> source, int take, Expression<Func<T, TProperty>> distinctBy)
var orderList = source.AsQueryable().GroupBy(distinctBy).OrderBy(x => x.Key).Take(take).SelectMany(x => x);
return orderList;
--- 已更新----
我从上面的语句中添加了生成的 SQL。
我创建了假表名JobItems
,列:Id
、Job
和Status
SELECT
[Extent2].[Id] AS [Id],
[Extent2].[Job] AS [Job],
[Extent2].[Status] AS [Status]
FROM (SELECT TOP (2) [Distinct1].[Job] AS [Job]
FROM ( SELECT DISTINCT
[Extent1].[Job] AS [Job]
FROM [dbo].[JobItems] AS [Extent1]
) AS [Distinct1]
ORDER BY [Distinct1].[Job] ASC ) AS [Limit1]
INNER JOIN [dbo].[JobItems] AS [Extent2] ON [Limit1].[Job] = [Extent2].[Job]
【讨论】:
谢谢!我认为这不会很好地转化为 SQL :( 对不起,我忘记了 SQL 部分。它不适用于 SQL :) @kofifus:Linq to SQL 支持请参考我最后的更新。 上面 OrderBy 中的Key
是什么?
@kofifus:这取决于distinctBy
参数。 Key
是 IGrouping<TKey, TElement>
的财产。在这种情况下,Key
是 Job
。【参考方案3】:
在 SQL 中,我会查看 WHERE IN 子查询。
SELECT JobId, Status
FROM Jobs
WHERE Status In (SELECT DISTINCT TOP 2 Status FROM Jobs)
没有太多使用 LinqToSQL 的经验,但这可能会直接起作用:
var TakeJobs = db.Jobs.Select(j => j.JobId).Distinct().Take(2);
var entries = db.Jobs.Where(j => TakeJobs.Contains(j.JobId));
显然,加入子查询可能会更便宜。我不确定它在 Linq 中的外观。
SELECT JobId, Status FROM Jobs j
RIGHT JOIN (SELECT DISTINCT TOP 2 JobId FROM Jobs) t ON t.JobId = j.JobId;
【讨论】:
以上是关于Linq 采取不同的方式的主要内容,如果未能解决你的问题,请参考以下文章