2009-06-17 76 views
3

我已经简化了这一点,因为我在寻找一个通用的答案。比方说,我有一个表的设置是这样的:Linq .Contains with large set cause TDS error

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

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

在我的C#程序,我经历了很多麻烦的是找父记录,我很感兴趣,并把它们塞进一个列表。父母是一张非常大的桌子,我宁愿不要查询它。所以我喜欢储存的钥匙:

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 }; 

这是所有伟大的,直到经济共同体包含超过2100条目。然后我得到一个TDS RPC错误(太多参数)。

我看到它,我能方式:

  • 不要在直线上升SQL整个事情(其实不想做经历的麻烦与一个DataReader,等...)。有一个涉及记录资格的外部系统,所以我不知道这是否完全有可能。另外,我会生成这个列表两次 - 一次当我需要在.Contains()中使用它,并再次用于其他目的。

  • 打破列表(录制),然后阅读块中的孩子。

如果我把它分解成块,然后我漂亮的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); 

因为列表区域经济共同体将包含需要组合在一起的东西,但必须为Linq查询拆分。

回答

2

我们使用SQL函数取值的一个varchar(最大值)分隔的列表作为参数并返回一个表变量。 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 
0

除了使用SQL和DataReader,您还可以编写两个存储过程并通过LINQ使用这些存储过程。或者甚至通过LINQ读取id列表并将其作为输入存储到您的存储过程中。

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

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

Somelinks解释如何编写和使用拆分字符串函数。顺便说一下,如果您使用的是SQL Server 2008,那么比字符串解析有更好的方法:table-valued parameters。您可以将一个表作为参数传递给您的存储过程。

+0

我的一个大问题是,在REC中限定记录并非全部都是在SQL中完成的 - 还有另一个系统涉及到。能够将这些密钥铲入临时表也是可以的,除非我不认为我可以从Linq获得临时表。 – 2009-06-17 16:56:53

+0

我更新了我的答案,并提供了有关如何解决问题的其他信息。 – 2009-06-23 07:05:53

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 

你可以用在你的字典建立代码相同的加入如下:

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); 
+0

不错的解决方案。 我唯一担心的是数据库中可能记录的数量非常大。数据是否被检索然后加入 - 在检索后消除不需要的记录,还是在SQL Server端消除了这些数据,然后将结果拉下来? – 2009-06-23 16:01:26

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();