【问题标题】:Working around the LINQ to SQL 2100 max parameter constraint in Where Contains解决 Where Contains 中的 LINQ to SQL 2100 最大参数约束
【发布时间】:2017-05-16 17:10:25
【问题描述】:

问题:我在 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


【解决方案1】:

不支持的构造是 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;
}

【讨论】:

  • 请注意,使用 LINQKit 之类的东西可以使这变得容易得多。
  • @NetMage 绝对!或任何组成助手的表达式。
  • 我感觉答案可能与表达有关。这正是我一直在寻找的。谢谢伊万!
【解决方案2】:

根据您使用的 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 问题,可以为您指明正确的方向。

【讨论】:

    【解决方案3】:

    我遇到了类似的情况,发现这种使用 AsEnumerable() 的惰性方法对我有用,但我被告知它将整个表加载到内存中,因此可能不可接受。

     .TableA
     .AsEnumerable()          
     .Where(x=>ids.Contains(x.RecordID))
     .ToList();
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-01-23
      • 1970-01-01
      • 2020-12-28
      • 1970-01-01
      相关资源
      最近更新 更多