加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 综合聚焦 > 服务器 > 安全 > 正文

performance – Scalable包含针对SQL后端的LINQ方法

发布时间:2020-12-16 18:53:38 所属栏目:安全 来源:网络整理
导读:我正在寻找一种以可扩展的方式执行 Contains() 语句的优雅方式.在我提出实际问题之前,请允许我给出一些背景知识. IN声明 在Entity Framework和LINQ to SQL中,Contains语句被翻译为SQL IN语句.例如,从这个声明: var ids = Enumerable.Range(1,10);var course
我正在寻找一种以可扩展的方式执行 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:

Including an extremely large number of values (many thousands) in an IN clause can consume resources and return errors 8623 or 8632

这与资源耗尽或超出表达限制有关.

但在发生这些错误之前,随着项目数量的增加,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);
    }
}

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读