Adding query results to a List, can it be done?

I've been testing linq and I'm wondering if this can be done.  The code compiles fine but crashes when run. I'm trying to get LINQ to create a new instance of my class for each result set that I then add to a typed list using foreach.  Now, is it not working because my class does not implement IEnumerable or is it some other reason

Here's the code snip I'm trying to get to work.



using System;
using System.Collections.Generic;
using System.Text;
using System.Query;
using System.Xml.XLinq;
using System.Data.DLinq;
using nwind;

namespace linq2
{
    public class Product
    {
        public int ProductID;
        public double UnitPrice;
    }

    class Program
    {
        static void Main(string[] args)
        {
            Northwind db = new Northwind("Data Source=(local);Initial Catalog=Northwind;Integrated Security=True");

            List<Product> plist = new List<Product>();

            IEnumerable<Product> query = from p in db.Products
                select new Product { ProductID = (int)p.ProductID, UnitPrice = (double)p.UnitPrice };

            foreach(Product p in query)
                    plist.Add((Product)p);

            Console.WriteLine("There are {0} items plist", plist.Count);
        }
    }
}

 


When run, this is the error returned:


Unhandled Exception: System.ArgumentException: GenericArguments[0], 'System.Int3
2', on 'System.Data.DLinq.SqlClient.MethodCallReader`1[T]' violates the constrai
nt of type 'T'.
   at System.RuntimeType.ValidateGenericArguments(MemberInfo definition, Type ty
peContext, MethodInfo methodContext, Type[] genericArguments, Type[] genericPara
mters)

 



Answer this question

Adding query results to a List, can it be done?

  • lzeca

    If you read the exception, you'll see it's saying that there's a problem with using System.Int32 in the reader.  It hasn't even reached the IEnumerable bit.  In fact, you got an exception, which means all this compiled, which means, at least, that query does in fact implement IEnumerable<Product>.

    I can't verify whether it'll solve your problem, but a couple observations:

    * ditch the explicit casts -- you almost certainly do not need them.  Let the system do its job and avoid micromanaging the code until absolutely necessary.  Without having run your code, I think it's the explicit casts which are throwing it off.
    * bite the bullet and declare query using var.  You won't lose strong typing.  It'll adapt as your query changes.  It'll also be correct.
    * try plist.AddRange(query).

    Overall -- and I'm generalizing here -- it looks like you need to sit down some more and have a long talk with the new APIs available in List (there are A LOT), as well as work through some misconceptions about where you need to make casts.  I think you'll be happier for it.



  • James Hicks

    Have you considered a type name conflict between elements contained in db.Products, and the Product class you have defined   Try removing or renaming your Product class.



  • snatch

    No prob..  It is entirely possible you've come across a bug.  I'm sure one of the MS folk will take a look.


  • Gaspar Nagy

    I'll set a break at the foreach and examine what is inside of the query and see if I can figure out why I need explicit casts in there.

    I renamed the Product class and used different named members and still I get the "argument type's do not match" message.

    Back to the debugger I go go.

    I found it easier to use the class defined by sqlmetal as a List type and extract the fields I needed from that like this, but I find I learn things easier when I do them my way.

    List<Products> plist = new List<Products>();
    var query = from p in db.Products orderby p.ProductID select p;

     


    At first, through timings, I found that ..AddRange was taking twice as long to process than a .Add in a foreach loop until I reversed the two and found .Add take the same time as the inital Addrange.  After the first .AddRange or .Add, even clearing the list and adding all of the values back in takes half the time the initial load did so /cheers local caching.

  • prof.gabi

    Strangely, removing the casts causes this (below) error which is why I put them in there to begin with.


    Unhandled Exception: System.ArgumentException: Argument types do not match
       at System.Expressions.Expression.Bind(MemberInfo member, Expression expr) in
    c:\PdcBuild\query\System.Query\expression.cs:line 106
       at System.Expressions.Expression.Bind(RuntimeFieldHandle fieldHandle, Express
    ion expr) in c:\PdcBuild\query\System.Query\expression.cs:line 117
       at linq2.Program.Main(String[] args) in d:\Vsp\C#\LINQ\linq2\linq2\Program.cs
    :line 29

     


    Line 29 being


    select new Product { ProductID = p.ProductID, SupplierID = p.SupplierID };

     


    Explicity casting them as both ints (as they are in the sql table) at least gets me past that part.  SQL shows both of those fields to be int's.  Even the generated nwind code declares "private int _ProductID;" yet without an explicit cast, this fails.

    Using the .addrange worked quite well.  The query works but, it will not work "on it's own" without the casts, go figure.

    I dont know whats considered "correct" following the use of 'var' and using my own IEnumerable type as even at the PDC there were developers showing both methods and reading msdn blog posts I see MS's own programmers using both methods.

    And given the time, I hope I get more free time to sit down and learn more about 2.0's features.  It's why I'm posting here after all.

  • Shajed Zaman

    Have you set breakpoints, and snooped the types involved   Before the enumeration (implied in AddRange, foreach, etc), break execution and examine the structure of query, and see what you have -- types, expression trees, etc.

    Try just "select p"

    Regarding "var":  at compile-time, "var" is replaced with whatever the variable initializer comes out to, type-wise.  Consider it a place-holder that says:  "specifying the actual type isn't important, or it's pretty complex to type -- just make the type correct based on the assignment".   This is different from using variant, or object, which says "it's impossible to say ahead of time what type this variable needs to contain".

    This is perfect for Linq, since the query's type can change based on using Linq, DLinq, XLinq, or whatever sort of fooLinq people come up with in the future.  All you, as a developer, need do is type "var foo = ...", and you're set.


  • Gallo_Teo

    Remember: the variable "query" is just a description of the query you want to perform -- you're not actually going to the database until the data is *first* accessed in the enumerator. 

    This may appear to be the reverse of what you're used to, but you can consider (at least for DLinq) that the analogue from ADO.NET is to create the connection, create a command, instantiate a reader, then loop over the reader.  In this case, you're creating a structure (stored in query) which describes the command, then DLinq does the actual command creation and reader instantiation for you when you ask for the Enumerator. 

    A similar thing goes on for Linq in general -- you're constructing an Enumerator in both cases -- but for Linq it's not interpreting some description (expression tree) of a description (sql) of a query operation (fetch the rows).

    If you want to time Add, foreach, AddRange, etc, then pull all the data *first* to get the database call taken care of.  Calling plist.GetEnumerator() should do it, I think.



  • beyonddc

    That explains the timing aspect of it.  I poked around and found that I really can't trace into the query operation itself without looking through asm code.

    Poking around more I found I can just as easily use

    List<Products> plist = query.ToList();
     
    without all of that mucking around I did before.

    I'll keep experimenting as to why I need to type those values even though they are equal to the types that sqlmetal created for the nwind database.  I'm going to try some queries now against my own SQL databases to see what happens.

    Thanks for taking the time to clarify the flow.  I finally got the dlinq.doc printed so now I can read it.

  • KYChen

    of course, that should have read "Calling query.GetEnumerator() should do it"...

  • Adding query results to a List, can it be done?