奇怪的LINQ to SQL .Union()错误

7 .net linq-to-sql

(请在底部查看完整的复制品)

使用以下实体......

[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; }
    }
}
Run Code Online (Sandbox Code Playgroud)

...此查询因NotSupportedException失败,并显示消息"Union in Union或Concat中的类型构造不兼容.":

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);
Run Code Online (Sandbox Code Playgroud)

我真的不明白为什么,两个查询都生成相同类型的实体,所以联合它们不应该是一个问题吗?

完整的repro如下:

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>();
    }
}
Run Code Online (Sandbox Code Playgroud)

afr*_*hke 4

该问题与允许外键列为空的事实有关.尝试将ManagerId列更改为不允许null(只需添加指向自身的"nobody"占位符值以表示层次结构的根)并再次尝试联合,它现在应该可以工作.不要问我为什么,仍然在挖掘Linq2Sql源代码......

更新(初步回答,超出我的头脑): 正如我所怀疑的,例外与可以ManagerId为空的事实有关.异常文本具有误导性:错误不会发生,因为两个查询结果是不兼容的类型,但是因为左侧和右侧查询的内部表示是不兼容的类型.Linq2Sql在找到时采用不同的代码路径,即FK(即ManagerId)可以为空.在您看到的查询中隐藏了一个连接,(employee.Manager),如果ManagerId是类型,Int32则Linq2Sql知道它可以执行内连接.ManagerId但是,如果是一个可以为空的int,那么Linq2Sql发现它需要进行左连接,即使在提供的示例中,由于filter子句,它可以通过内连接逃脱.

解决该问题的一种方法是在执行联合之前实现所讨论的一个或两个查询(即,调用.ToList()或另一个合适的扩展方法).