tags:

views:

459

answers:

3

I've oversimplified this a bit, because I'm looking for a general-purpose answer. Let's say I've got a table setup like this:

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

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

And in my C# program I've gone through a lot of hassle to find the Parent records that I'm interested in and stuff them into a list. Parent is a really large table, and I'd rather not query it more than necessary. So I squirrel away the keys:

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

Later on in Linq I can say, find all of the child records for the associated parents:

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

This is all great until recs contains more than 2100 entries. Then I get a TDS RPC error (too many parameters).

The way I see it I can:

  • Do the whole thing in straight up SQL (didn't really want to do go through the hassle with a DataReader, etc...). There was an external system involved in qualifying the records, so I don't know if that's entirely possible either. Plus, I'd be generating that list twice -- once when I need to use it in .Contains(), and again for other purposes.

  • Break the list (recs) up, and then read Child in chunks.

If I break it up in chunks, then my pretty Linq a little farther down won't work at all:

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

Because the List recs will contain things that needed to be grouped together, but necessarily split apart for the Linq query.

A: 

Instead of using SQL and a DataReader you could also write two stored procedures and use those via LINQ. Or even read the list of id's via LINQ and feed that as input to your stored procedures.

You probably won't be able to solve the too many parameters issue (do you have access to the database) and the chunk solution isn't very nice and doesn't solve the whole problem because of the second query.

EDIT: Since the recs collection is not entirely database-generated, you need some way to tell your database the contents of this collection. I think your best option would be to use a stored procedure (or two) that accepts the collection as a large comma-separated string. Inside the stored procedure you split the string into ids again.

Some links that explain how to write and use a split string function.

By the way, if you are using SQL Server 2008, there is a far better approach than string parsing: table-valued parameters. You can pass a table as parameter to your stored procedure.

Ronald Wildenberg
My big problem is that qualifying the records in recs wasn't all done in SQL -- there's another system involved. Being able to shovel those keys into a temporary table would work too, except I don't think I can get to that temp table from Linq either.
clintp
I updated my answer with some additional information on how to solve the problem.
Ronald Wildenberg
A: 

This looks like a job for Linq .Join() . I've used objects below instead of a database as the data source but the concept is the same if your LinqToSql provider supports it.

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

output:

2000
2500

You can use the same Join in your Dictionary creation code as follows:

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);
Handcraftsman
Nice solution.My only worry is when the number of possible records in the database is very very large. Does the data get retrieved and then joined -- eliminating the unwanted records after retrieval, or do they get eliminated on the SQL Server side and then the result pulled over?
clintp
This doesn't work. You attempt to use a client-side IEnumerable (recs) in a SQL query (this is only supported for Contains, which gets translated to an IN expression). Ask yourself how LINQtoSQL should inform the database of the contents of recs. The only way this will work is when you first get the contents of the entire Childs table to the client and then join with recs.
Ronald Wildenberg
Fetching just the ChildId and ParentRecordId to do the join on the client side might be less expensive. Follow this up with queries for the Child details of the matched records and/or break it up into chunks less than 2100, perhaps using an InSetsOf implementation http://stackoverflow.com/questions/1034429/how-to-prevent-memory-overflow-when-using-an-ienumerablet-and-linq-to-sql/1035039#1035039 . Finally, it appears SQL Server 2008, if that is an option Clint, added Table-Valued Parameters http://msdn.microsoft.com/en-us/library/bb675163.aspx as a way around the 2100 parameter limit.
Handcraftsman
A: 

I think this is what you're looking for:

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

So... I don't know what condition you're using for getting the parents, but hopefully it can be done with a lambda expression, such as:

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