解决Where Contains中LINQ to SQL 2100最大参数约束的问题

问题描述 投票:3回答:3

问题:我在SQL Server数据库上使用SQLMetal生成了一个DataContext。数据库具有TableA,其中包含具有Int64标识符的实体。我的查询需要处理我在查询所有带有ID的元素的情况。随着数据集的增长,这个集合偶尔会包含超过2100个ID。

我意识到这个问题与其他有关该主题的问题类似,但我正在寻求帮助构建一个扩展方法来解决这个问题。

相关问题: Avoiding the 2100 parameter limit in LINQ to SQL Hitting the 2100 parameter limit (SQL Server) when using Contains()

我的代码看起来像这样:

var ids = new List<long>{ 1, 2, 3, /*...,*/ 2101};
var database = new MyDatabaseClass(connection)
var items = database
 .TableA
 .Where(x=>ids.Contains(x.RecordID))
 .ToList();

并产生此错误:

传入的表格数据流(TDS)远程过程调用(RPC)协议流不正确。此RPC请求中提供的参数太多。最高为2100。

随着各种数据集的增长,我预计会遇到很多这个问题。我想创建一个可以用于任何表的通用扩展。我们的想法是将查询分解为较小的Where Contains查询,然后汇总结果。这是我尝试展示我的想法之一:

public static List<TSource> WhereMemberInUniverse<TSource, TUniverse>(this IQueryable<TSource> source, Func<TSource, TUniverse> memberSelector, IEnumerable<TUniverse> universe)
{
    var distinctUniverse = universe.Distinct().ToList();
    int batchSize = 2000;

    var result = new List<TSource>();

    for (int i = 0; i < distinctUniverse.Count; i += batchSize)
    {
        var universeSlice = distinctUniverse.Skip(i).Take(batchSize);
        var partialRes = source
            .Where(x => universeSlice.Contains(memberSelector(x)));
        result.AddRange(partialRes);
    }

    return result;
}

调用代码将被修改为:

var ids = new List<long>{ 1, 2, 3, /*...,*/ 2101};
var database = new MyDatabaseClass(connection)
var items = database
.TableA
.WhereMemberInUniverse(x=>x.RecordID, ids);

现在,这是'universeSlice.Contains'行的失败:

方法'System.Object DynamicInvoke(System.Object [])'没有支持的SQL转换。

似乎我所做的每一次尝试都以类似的SQL翻译错误结束。

c# sql-server linq linq-to-sql
3个回答
4
投票

不受支持的构造是LINQ查询表达式树中的memberSelector(x)调用。

要使LINQ查询可翻译(这基本上适用于任何IQueryable提供程序),您需要更改参数类型

Func<TSource, TUniverse> memberSelector

Expression<Func<TSource, TUniverse>> memberSelector

然后建立

x => universeSlice.Contains(memberSelector(x))

动态使用System.Linq.Expressions.Expression类方法:

public static List<TSource> WhereMemberInUniverse<TSource, TUniverse>(this IQueryable<TSource> source, Expression<Func<TSource, TUniverse>> memberSelector, IEnumerable<TUniverse> universe)
{
    var distinctUniverse = universe.Distinct().ToList();
    int batchSize = 2000;

    var result = new List<TSource>();

    for (int i = 0; i < distinctUniverse.Count; i += batchSize)
    {
        var universeSlice = distinctUniverse.Skip(i).Take(batchSize);
        //x => universeSlice.Contains(memberSelector(x))
        var predicate = Expression.Lambda<Func<TSource, bool>>(
            Expression.Call(
                typeof(Enumerable), "Contains", new Type[] { typeof(TUniverse) },
                Expression.Constant(universeSlice), memberSelector.Body
            ),
            memberSelector.Parameters
        );
        var partialRes = source.Where(predicate);
        result.AddRange(partialRes);
    }

    return result;
}

0
投票

根据您使用的SQL Server版本,您应该查看Table Valued Parameters

表值参数提供了一种简单的方法,可以将多行数据从客户端应用程序封送到SQL Server,而无需多次往返或特殊的服务器端逻辑来处理数据。您可以使用表值参数来封装客户端应用程序中的数据行,并使用单个参数化命令将数据发送到服务器。传入的数据行存储在表变量中,然后可以使用Transact-SQL对其进行操作。

首先,您需要在SQL Server中创建类型,例如:

CREATE TYPE [dbo].[MyIdTable] AS TABLE(
    [Id] [int] NOT NULL
)

然后你可以创建一个System.Data.DataTable并用你的ID填充它:

var table = new DataTable("MyIdTable");
table.Columns.Add("Id", typeof(int));
foreach (var id in ids)
    table.Rows.Add(id);

并将其作为参数发送到SQL /存储过程。但是,据我所知,L2SQL不支持TVP。所以你可能需要深入了解原始的ADO.net。 Here's一个Stack Overflow问题可以指向正确的方向。


0
投票

我遇到了类似的问题,发现这个使用AsEnumerable()的懒惰方法对我有用,但我告诉它将整个表加载到内存中,所以可能无法接受。

 .TableA
 .AsEnumerable()          
 .Where(x=>ids.Contains(x.RecordID))
 .ToList();
© www.soinside.com 2019 - 2024. All rights reserved.