(See at the bottom for a full repro)
With the following entity …
[Table]
internal sealed class Employee
{
private EntityRef<Employee> manager;
[Column(IsPrimaryKey = true, IsDbGenerated = true)]
private int Id;
[Column]
private int? ManagerId;
[Column]
internal bool IsOverpaid;
[Association(Name = "Manager_Subordinate", Storage = "manager", ThisKey = "ManagerId", IsForeignKey = true)]
internal Employee Manager
{
get { return this.manager.Entity; }
set { this.manager.Entity = value; }
}
}
… this query fails with a NotSupportedException, with the message “Types in Union or Concat are constructed incompatibly.”:
var overpaidTopManagers =
from employee in context.Employees
where employee.IsOverpaid && (employee.Manager == null)
select employee;
var managersWithOverpaidSubordinates =
from employee in context.Employees
where employee.IsOverpaid && (employee.Manager != null)
select employee.Manager;
var query = overpaidTopManagers.Union(managersWithOverpaidSubordinates);
I don’t really understand why, both queries produce the same type of entity, so it shouldn’t be a problem to union them?
Full repro follows:
using System;
using System.Data.Linq;
using System.Data.Linq.Mapping;
using System.Linq;
internal static class Program
{
private static void Main(string[] args)
{
using (var context = new Context("Whatever.sdf"))
{
if (!context.DatabaseExists())
{
context.CreateDatabase();
}
var overpaidTopManagers =
from employee in context.Employees
where employee.IsOverpaid && (employee.Manager == null)
select employee;
var managersWithOverpaidSubordinates =
from employee in context.Employees
where employee.IsOverpaid && (employee.Manager != null)
select employee.Manager;
var query = overpaidTopManagers.Union(managersWithOverpaidSubordinates);
// This throws a NotSupportedException with the Message
// "Types in Union or Concat are constructed incompatibly."
foreach (var manager in query)
{
Console.WriteLine(manager.ToString());
}
}
}
}
[Table]
internal sealed class Employee
{
private EntityRef<Employee> manager;
[Column(IsPrimaryKey = true, IsDbGenerated = true)]
private int Id;
[Column]
private int? ManagerId;
[Column]
internal bool IsOverpaid;
[Association(Name = "Manager_Subordinate", Storage = "manager", ThisKey = "ManagerId", IsForeignKey = true)]
internal Employee Manager
{
get { return this.manager.Entity; }
set { this.manager.Entity = value; }
}
}
internal sealed class Context : DataContext
{
internal Table<Employee> Employees;
internal Context(string fileOrServerOrConnection) : base(fileOrServerOrConnection)
{
this.Employees = this.GetTable<Employee>();
}
}
The problem is related to the fact that the foreign key column is allowed to be null. Try changing the ManagerId column to not allow null (just add a “nobody” placeholder value pointing to itself to represent the root of the hierarchy) and try the union again, it should now work. Don’t ask me why though, still digging through the Linq2Sql source code…
Update (preliminary answer, out of the top of my head):
As I suspected, the exception has to do with the fact that
ManagerIdis nullable. The exception text is misleading: The error doesn’t occur because the two query results are of incompatible type, but because the internal representations of the left and the right query are of incompatible types. Linq2Sql takes a different code path when it finds, that the FK (i.e.ManagerId) is nullable. There is a join hidden in the query you see, (employee.Manager) and ifManagerIdis of typeInt32then Linq2Sql knows that it can perform an inner join. IfManagerIdis a nullable int however, then Linq2Sql finds that it needs to do a left join, even though in the example provided it could get away with an inner join because of the filter clause.One way around the issue is to materialize one or both of the queries in question (i.e. call .ToList() or another suitable extension method) prior to performing the union.