Jonathan Wood
Jonathan Wood

Reputation: 67315

Concat Two IQueryables with Anonymous Types?

I've been wrestling with this a little while and it's starting to look like it may not be possible.

I want to Concat() two IQueryables and then execute the result as a single query. I tried something like this:

var query = from x in ...
select new
{
    A = ...
    B = ...
    C = ...
};

var query2 = from y in ...
select new
{
    A = ...
    B = ...
    C = ...
};

var query3 = query.Concat(query2);

However, the last line gives me the following error:

'System.Linq.IQueryable' does not contain a definition for 'Concat' and the best extension method overload 'System.Linq.ParallelEnumerable.Concat(System.Linq.ParallelQuery, System.Collections.Generic.IEnumerable)' has some invalid arguments

It appears it's expecting an IEnumerable for the argument. Is there any way around this?

It looks like I could resolve both queries to IEnumerables and then Concat() them. But it would be more efficient to create a single query, and it seems like that should be possible.

Upvotes: 11

Views: 7734

Answers (5)

Jeff Mercado
Jeff Mercado

Reputation: 134571

Anonymous objects will be equivalent types to other anonymous objects if they have the same property names and types declared in exactly the same order.

Assuming both query and query2 come from the same contexts, you should be able to combine the two, provided they are queries of equivalent types.

Your comment indicates that neither are of the same type.

query returns objects of type Anon<FileItem, Employee, int, string, string>
query2 returns objects of type Anon<FileItem, Employee, int?, string, string>.

You cannot combine the two because they are of different types however they are similar enough that you can (with appropriate conversions). You'll need to make sure that both queries return objects of the same type by ensuring they have the same properties with the same types in the same order.

var query = from x in ...
    select new
    {
        A = (FileItem)x.A,
        B = (Employee)x.b,
        C = (int?)x.C, // int can be safely cast to int?
        ...
    };

var query2 = from y in ...
    select new
    {
        A = (FileItem)y.A,
        B = (Employee)y.B,
        C = (int?)y.C, // already an int?
        ...
    };

var query3 = query.Concat(query2); // works since both are Anon<FileItem, Employee, int?, ...>

Upvotes: 4

Zodman
Zodman

Reputation: 3924

You can't. Even though the two anonymous types look the same they are two different types.

The most-readable-code approach in this is case is to declare a type to Select your values into for your two queries. Then the Concat will be working on a single type and do what you need.

You haven't specified the types of A, B or C so I'm going to cast them to TypeA, TypeB, and TypeC and make them properties of collections of SomeType for purposes of demonstrating Concat.

class Result
{
    public TypeA A { get; }
    public TypeB B { get; }
    public TypeC C { get; }

    public Result(TypeA a, TypeB b, TypeC c)
    {
        A = a;
        B = b;
        C = c;
    }
}

ICollection<SomeType> source1;
ICollection<SomeType> source2;

// sources get populated here somehow

var result1 = source1.Select(_ => new Result(_.A, _.B, _.C));
var result2 = source2.Select(_ => new Result(_.A, _.B, _.C));
var results = result1.Concat(result2);

Upvotes: 1

Conrad Clark
Conrad Clark

Reputation: 4536

You can't.

As you said previously in the comments, it seems that the two queries return different objects:

Query 1 (as per comment):

f__AnonymousTypee<Leo.Domain.FileItem,Leo.Domain.Employ‌​ee,int,string,string>

Query2 is

f__AnonymousTypee<Leo.Domain.FileItem,L‌​eo.Domain.Employee,int?,string,string>

This is why Concat is giving you an error message complaining about invalid arguments.

Upvotes: 9

Johnny
Johnny

Reputation: 491

The IDE determined query and query2 are of different types, while the IEnumerable<TSource> Concat<TSource>() extension method expects two same types (IEnumerable<TSource>). The three TSource's must be the same.

string[] strA = {"123", "234", "345"};
int[] intA = { 1, 2, 3 };
var query = from s in strA
            select s;
var query2 = from i in strA // intA
                select i;
var query3 = query.Concat(query2);

Uncomment "// intA" in VS and you'll see the difference.

Upvotes: 1

Kajal Sinha
Kajal Sinha

Reputation: 1575

Are you missing any namespace? Normally I mark my .NET Project Properties to target .net 4.0 for vs 2010. I do not use .net 4.0 Client Profile.

Please make sure that the types of A, B and C is matches in both the query anonymous types. Also the order of A, B and C should also match in both queries.

The following example works like a charm.

namespace Test
{
    using System;
    using System.Collections.Generic;
    using System.Linq;

    internal class Employee
    {
        public string Name { get; set; }
        public int Age { get; set; }
        public double Salary { get; set; }
        public string Address { get; set; }
    }

    internal class Program
    {
        private static List<Employee> employees = new List<Employee>();

        private static void BuildList()
        {
            employees.AddRange(
                new Employee[]
                    {
                        new Employee() {Name = "Tom", Age = 22, Address = "sample1", Salary = 10000},
                        new Employee() {Name = "Mithun", Age = 27, Address = "sample1", Salary = 20000},
                        new Employee() {Name = "Jasubhai", Age = 24, Address = "sample1", Salary = 12000},
                        new Employee() {Name = "Vinod", Age = 34, Address = "sample1", Salary = 30000},
                        new Employee() {Name = "Iqbal", Age = 52, Address = "sample1", Salary = 50000},
                        new Employee() {Name = "Gurpreet", Age = 22, Address = "sample1", Salary = 10000},

                    }
                );
        }

        private static void Main(string[] args)
        {
            BuildList();
            var query = from employee in employees
                        where employee.Age < 27
                        select new
                            {
                                A = employee.Name,
                                B = employee.Age,
                                C = employee.Salary
                            };


            var query2 = from employee in employees
                         where employee.Age > 27
                         select new
                             {
                                 A = employee.Name,
                                 B = employee.Age,
                                 C = employee.Salary
                             };

            var result = query.Concat(query2);

            foreach (dynamic item in result.ToArray())
            {
                Console.WriteLine("Name = {0}, Age = {1}, Salary = {2}", item.A, item.B, item.C);
            }
        }


    }
}

Upvotes: -1

Related Questions