performance – Scalable包含针对SQL后端的LINQ方法
我正在寻找一种以可扩展的方式执行
Contains() 语句的优雅方式.在我提出实际问题之前,请允许我给出一些背景知识.
IN声明 在Entity Framework和LINQ to SQL中,Contains语句被翻译为SQL IN语句.例如,从这个声明: var ids = Enumerable.Range(1,10); var courses = Courses.Where(c => ids.Contains(c.CourseID)).ToList(); 实体框架将生成 SELECT [Extent1].[CourseID] AS [CourseID],[Extent1].[Title] AS [Title],[Extent1].[Credits] AS [Credits],[Extent1].[DepartmentID] AS [DepartmentID] FROM [dbo].[Course] AS [Extent1] WHERE [Extent1].[CourseID] IN (1,2,3,4,5,6,7,8,9,10) 不幸的是,In语句不可扩展.根据MSDN:
这与资源耗尽或超出表达限制有关. 但在发生这些错误之前,随着项目数量的增加,IN语句变得越来越慢.我无法找到有关其增长率的文档,但它可以很好地执行数千个项目,但除此之外它会变得非常缓慢. (基于SQL Server经验). 可扩展 我们不能总是避免这种说法.与源数据的JOIN通常会表现得更好,但这只有在源数据处于相同的上下文时才有可能.在这里,我正在处理来自断开连接的场景中的客户端的数据.所以我一直在寻找可扩展的解决方案.一个令人满意的方法是将操作切成块: var courses = ids.ToChunks(1000) .Select(chunk => Courses.Where(c => chunk.Contains(c.CourseID))) .SelectMany(x => x).ToList(); (其中ToChunks是this的小扩展方法). 这将以1000块为单位执行查询,这些查询都表现良好.用例如将运行5000个项目,5个查询,这些查询一起可能比具有5000个项目的一个查询更快. 但不是干 但是我当然不希望在我的代码中分散这个构造.我正在寻找一种扩展方法,通过该方法,任何IQueryable< T>可以转换成一个厚实的执行语句.理想情况下是这样的: var courses = Courses.Where(c => ids.Contains(c.CourseID)) .AsChunky(1000) .ToList(); 但也许这个 var courses = Courses.ChunkyContains(c => c.CourseID,ids,1000) .ToList(); 我给了后一个解决方案第一枪: public static IEnumerable<TEntity> ChunkyContains<TEntity,TContains>( this IQueryable<TEntity> query,Expression<Func<TEntity,TContains>> match,IEnumerable<TContains> containList,int chunkSize = 500) { return containList.ToChunks(chunkSize) .Select (chunk => query.Where(x => chunk.Contains(match))) .SelectMany(x => x); } 显然,部分x => chunk.Contains(match)不编译.但我不知道如何将匹配表达式操作为Contains表达式. 也许有人可以帮我解决这个问题.当然,我愿意接受其他方法来使这个语句可扩展. 解决方法
我在一个月之前用一种不同的方法解决了这个问题.也许这对你来说也是一个很好的解决方案.
我不希望我的解决方案改变查询本身.所以ids.ChunkContains(p.Id)或特殊的WhereContains方法是不可行的.该解决方案也应该能够将Contains与另一个过滤器结合使用,以及多次使用相同的集合. db.TestEntities.Where(p => (ids.Contains(p.Id) || ids.Contains(p.ParentId)) && p.Name.StartsWith("Test")) 所以我尝试将逻辑封装在一个特殊的ToList方法中,该方法可以重写表达式,以便在块中查询指定的集合. var ids = Enumerable.Range(1,11); var result = db.TestEntities.Where(p => Ids.Contains(p.Id) && p.Name.StartsWith ("Test")) .ToChunkedList(ids,4); 为了重写表达式树,我在查询中发现了来自本地集合的所有Contains方法调用,并提供了视图帮助类. private class ContainsExpression { public ContainsExpression(MethodCallExpression methodCall) { this.MethodCall = methodCall; } public MethodCallExpression MethodCall { get; private set; } public object GetValue() { var parent = MethodCall.Object ?? MethodCall.Arguments.FirstOrDefault(); return Expression.Lambda<Func<object>>(parent).Compile()(); } public bool IsLocalList() { Expression parent = MethodCall.Object ?? MethodCall.Arguments.FirstOrDefault(); while (parent != null) { if (parent is ConstantExpression) return true; var member = parent as MemberExpression; if (member != null) { parent = member.Expression; } else { parent = null; } } return false; } } private class FindExpressionVisitor<T> : ExpressionVisitor where T : Expression { public List<T> FoundItems { get; private set; } public FindExpressionVisitor() { this.FoundItems = new List<T>(); } public override Expression Visit(Expression node) { var found = node as T; if (found != null) { this.FoundItems.Add(found); } return base.Visit(node); } } public static List<T> ToChunkedList<T,TValue>(this IQueryable<T> query,IEnumerable<TValue> list,int chunkSize) { var finder = new FindExpressionVisitor<MethodCallExpression>(); finder.Visit(query.Expression); var methodCalls = finder.FoundItems.Where(p => p.Method.Name == "Contains").Select(p => new ContainsExpression(p)).Where(p => p.IsLocalList()).ToList(); var localLists = methodCalls.Where(p => p.GetValue() == list).ToList(); 如果在查询表达式中找到了在ToChunkedList方法中传递的本地集合,我将对原始列表的Contains调用替换为包含一个批处理的id的临时列表的新调用. if (localLists.Any()) { var result = new List<T>(); var valueList = new List<TValue>(); var containsMethod = typeof(Enumerable).GetMethods(BindingFlags.Static | BindingFlags.Public) .Single(p => p.Name == "Contains" && p.GetParameters().Count() == 2) .MakeGenericMethod(typeof(TValue)); var queryExpression = query.Expression; foreach (var item in localLists) { var parameter = new List<Expression>(); parameter.Add(Expression.Constant(valueList)); if (item.MethodCall.Object == null) { parameter.AddRange(item.MethodCall.Arguments.Skip(1)); } else { parameter.AddRange(item.MethodCall.Arguments); } var call = Expression.Call(containsMethod,parameter.ToArray()); var replacer = new ExpressionReplacer(item.MethodCall,call); queryExpression = replacer.Visit(queryExpression); } var chunkQuery = query.Provider.CreateQuery<T>(queryExpression); for (int i = 0; i < Math.Ceiling((decimal)list.Count() / chunkSize); i++) { valueList.Clear(); valueList.AddRange(list.Skip(i * chunkSize).Take(chunkSize)); result.AddRange(chunkQuery.ToList()); } return result; } // if the collection was not found return query.ToList() return query.ToList(); 表达式替换者: private class ExpressionReplacer : ExpressionVisitor { private Expression find,replace; public ExpressionReplacer(Expression find,Expression replace) { this.find = find; this.replace = replace; } public override Expression Visit(Expression node) { if (node == this.find) return this.replace; return base.Visit(node); } } (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |