The first-level solution is to cast both Individuals
and Corporate
back to the base class, like this:
var items =
context.Individuals.Select(x => (Person)x)
.Concat(context.Corporations.Select(x => (Person)x))
.OrderBy(x => x.Name);
This will give you an enumeration of Person
objects, which could be either sub-class: Individuals
or Corporate
. Casting to the common base class solves the compatibility issue, but now you're restricted to using only those properties available on the base class. You'll need to do type checks on each item to figure out which concrete type it is if you need to access properties specific to one or other sub-class (the Profession
reference on an Individuals
object for instance).
Simple display:
foreach (var person in items)
{
Console.WriteLine("{0,-6}{1,-12}{2}", person.Id, person.Name, person.GetType().Name);
}
With the data above this should display:
1 Person 1 Individuals
3 Person 1 Corporate
2 Person 2 Individuals
4 Person 2 Corporate
As discussed in the comments, the casting of the objects to the Person
type gets done before the Include()
statement in my original query, which causes an error at runtime when Entity Frameworks can't find the Profession
property of the People
class.
I cam see three ways to deal with this:
Let EF handle it as a lazy load.
Add an operation that forces the data into memory before the conversion
Use another type for the results.
The first option is to not include the Profession
property in the query at all and let Entity Frameworks do the work for you. This is the lazy method for quick-n-dirty queries that you're expecting to return only a handful of records. Whenever you reference the Profession
property of an Individuals
object for the first time EF will query the database server for it. For large collections of records this is A Bad Idea. If you had 1000 Individuals
records, that's an extra 1000 SQL queries that have to be run.
Second option looks like this:
var items =
context.Individuals.Include("Profession").ToArray().Select(x => (Person)x)
.Concat(context.Corporations.Select(x => (Person)x)
.OrderBy(x => x.Name);
The ToArray
operation in the midst of the expression will tell EF to stop building the query and go grab the data, returning a Individuals[]
array. Unfortunately this is pretty inefficient for large data sets.
And the final option:
var items =
context.Individuals.Select(x => new
{
Id = x.Id,
Name = x.Name,
Profession = x.Profession.Description,
Type = "Individual"
})
.Concat(context.Corporations.Select(x => new
{
Id = x.Id,
Name = x.Name,
Profession = (string)null,
Type = "Corporation"
}))
.OrderBy(x => x.Name).ThenBy(x => x.Id);
I've used compatible anonymous types - same field names and types in the same order - but you could also use a specific result record structure or class. Use anonymous types if all the processing is happening locally, or result struct/class if you'll be passing the information outside of the method.
In general I think that the last one is the best of the 3. Use an intermediate type to store the results and you'll get a much better result than if you play around with type casting, forcing data into memory, etc. You also get a much more efficient query, since only those fields you request in your queries will be returned from the server. Great when you only want two columns from a table with lots and lots of columns. When you get to a table design with 50+ columns it makes a lot of difference.
Incidentally, my original idea of printing person.GetType().Name
was a bit broken. EF subclasses your classes, so the types - and therefore the names - you get out will be ugly mashups. The third option solves this by explicitly setting the values.