Linq。包含大量设置会导致TDS错误

时间:2009-06-17 15:42:38

标签: linq tsql

我过分简化了这一点,因为我正在寻找一个通用的答案。假设我有一个像这样的表格设置:

Parent
recno    int (unique, pk)
date     datetime
stuff    varchar(50)

Child
parentrecno (int, fk)   --- PK
sequence    (int)       --- PK
data     varchar(50)

在我的C#程序中,我经历了很多麻烦,找到了我感兴趣的父记录并将它们填入列表中。 Parent是一个非常大的表,我宁愿不查询它。所以我松开了钥匙:

List<int> recs = (from d in Parent where [.....] select d.recno).ToList();

稍后在Linq我可以说,找到相关父母的所有子记录:

var kids = from k in database.Childs
      where recs.Contains(k.parentrecno)
      select new { k };

在recs包含超过2100个条目之前,这一切都很棒。然后我收到TDS RPC错误(参数太多)。

我能看到它的方式:

  • 直接用SQL做整件事(真的不想用DataReader来解决麻烦等等)。有一个外部系统涉及对记录进行鉴定,所以我不知道这是否完全可能。另外,我会生成两次列表 - 一次我需要在.Contains()中使用它,再次用于其他目的。

  • 打破列表(recs),然后以块的形式阅读Child。

如果我把它分成几块,那么我漂亮的Linq会更加向下一点:

var kids2 = (from kid in paydb.Childs
         where
             recs.Contains(kid.parentrecno)
         group pay by kid.parentrecno into kgroup
         select new { ParentRecNo = kgroup.Key, KidRecords = kgroup })
              .ToDictionary(kx => kx.ParentRecNo);

因为List recs将包含需要组合在一起的内容,但必须为Linq查询拆分。

4 个答案:

答案 0 :(得分:2)

我们使用SQL函数将varchar(max)分隔的值列表作为参数并返回表变量。 linq看起来像这样:

from a in Context.SomeTable    
join nl in Context.ParseDelimited(nodeIdList) on a.NodeId.ToString() equals nl.FieldValue

其中nodeIdList是一个字符串变量,包含上一个查询中的id列表。很丑,但它确实绕过2100参数限制。

create function dbo.ParseDelimited(@delimitedList NVARCHAR(MAX)) returns @tblSample table(counter int, fieldValue NVARCHAR(100)) 
WITH SCHEMABINDING
as
begin
  declare @counter    NVARCHAR(  4)
  declare @fieldValue NVARCHAR(100)

  declare @tmpTable table(counter int primary key, fieldValue NVARCHAR(100))

  set @counter = 1

  while charindex(',', @delimitedList) > 0
  begin
    set @fieldValue = ltrim(rtrim(substring(@delimitedList, 1, charIndex(',', @delimitedList)-1)))

    insert into @tmpTable select @counter, @fieldValue

    set @delimitedList = ltrim(rtrim(substring(@delimitedList, (charindex(',', @delimitedList) + 1), len(@delimitedList))))

    set @counter = @counter + 1
  end

  if ltrim(rtrim(@delimitedList)) != ''
  begin
    insert into @tmpTable select @counter, @delimitedList
  end

  insert into @tblSample select counter, fieldValue from @tmpTable

  return
end

答案 1 :(得分:0)

您可以编写两个存储过程并通过LINQ使用它们,而不是使用SQL和DataReader。或者甚至通过LINQ读取id列表并将其作为输入提供给存储过程。

您可能无法解决太多的参数问题(您是否有权访问数据库)并且块解决方案不是很好,并且由于第二个查询而无法解决整个问题。 / p>

编辑:由于recs集合并非完全由数据库生成,因此您需要某种方法来告知数据库此集合的内容。我认为你最好的选择是使用一个存储过程(或两个)接受集合作为一个大的逗号分隔字符串。在存储过程中,您再次将字符串拆分为ID。

Some links解释了如何编写和使用拆分字符串函数。

顺便说一句,如果您使用的是SQL Server 2008,那么有一种比字符串解析更好的方法:table-valued parameters。您可以将表作为参数传递给存储过程。

答案 2 :(得分:0)

这看起来像Linq .Join()的工作。我使用下面的对象而不是数据库作为数据源,但如果您的LinqToSql提供程序支持它,则概念是相同的。

List<int> recs = new List<int>(Enumerable.Range(1, 2500));
List<Child> children = new List<Child>(Enumerable.Range(2000, 1000)
    .Select(x => new Child
    {
        ParentRecNo = x
    }));

var kids = children.Join(recs, x => x.ParentRecNo, y => y, (x, y) => x);

Console.WriteLine(kids.First().ParentRecNo);
Console.WriteLine(kids.Last().ParentRecNo);

输出:

2000
2500

您可以在字典创建代码中使用相同的Join,如下所示:

var kids2 = children
    .Join(recs, x => x.ParentRecNo, y => y, (x, y) => x)
    .GroupBy(x => x.ParentRecNo)
    .Select(kgroup => new
        {
            ParentRecNo = kgroup.Key,
            KidRecords = kgroup
        })
    .ToDictionary(kx => kx.ParentRecNo);

答案 3 :(得分:0)

我认为这就是你要找的东西:

List<Child> children = 
database.Parents.Where( 'your favourite lambda expression').Select(x=>x.Childs).ToList();

所以...我不知道你用什么条件来获取父母,但希望可以用lambda表达式完成,例如:

List<Child> children = database.Parents
     .Where(p=>p.recno > 10 && p.recno < 40)
     .Select(x=>x.Childs).ToList();