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

时间:2011-07-26 19:46:52

标签: .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; }
    }
}

...此查询因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);

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

完全重复:

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>();
    }
}

2 个答案:

答案 0 :(得分:5)

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

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

解决问题的一种方法是在执行联合之前实现一个或两个有问题的查询(即调用.ToList()或其他合适的扩展方法)。

答案 1 :(得分:3)

另一个答案是正确的。这是一种不需要将所有结果加载到应用程序空间的解决方法:

var overpaidTopManagers =
  from employee in context.Employees
  where employee.IsOverpaid && (employee.Manager == null)
  select employee;
var managersWithOverpaidSubordinates =
  from employee in context.Employees
  from manager in context.Employees
  where manager == employee.Manager
  select manager;
var query = overpaidTopManagers.Union(managersWithOverpaidSubordinates);

因为managersWithOverpaidSubordinates现在从主Employees表中选择(带有显式连接),所以Linq to SQL理解它是一个不包含空值的源,并且同意执行联合。