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

2023-09-04 08:30:37 作者:呆喜

(为一个完整的摄制参见在底部)

通过下面的实体...

  [表]
内部密封类员工
{
    私人的EntityRef<员工>经理;

    [专栏(IsPrimaryKey = TRUE,IsDbGenerated =真)
    私人诠释编号;

    [柱]
    私人诠释?经理ID;

    [柱]
    内部布尔IsOverpaid;

    [公会(NAME =Manager_Subordinate,存储=经理,ThisKey =经理ID,IsForeignKey =真)
    内部员工管理
    {
        {返回this.manager.Entity; }
        集合{this.manager.Entity =价值; }
    }
}
 

...此查询失败,NotSupportedException异常,并显示消息在欧盟或Concat的类型是不兼容的构建。

  VAR overpaidTopManagers =
    从员工的context.Employees
    其中,employee.IsOverpaid和放大器;&安培; (employee.Manager == NULL)
    选择员工;
VAR managersWithOverpaidSubordinates =
    从员工的context.Employees
    其中,employee.IsOverpaid和放大器;&安培; (employee.Manager!= NULL)
    选择employee.Manager;
VAR的查询= overpaidTopManagers.Union(managersWithOverpaidSubordinates);
 

我真的不明白为什么,这两个查询产生相同类型的实体,所以它不应该是一个问题,工会呢?

全摄制如下:

 使用系统;
使用System.Data.Linq程序;
使用System.Data.Linq.Mapping;
使用System.Linq的;

内部静态类节目
{
    私有静态无效的主要(字串[] args)
    {
        使用(VAR上下文=新的上下文(Whatever.sdf))
        {
            如果(!context.DatabaseExists())
            {
                context.CreateDatabase();
            }

            VAR overpaidTopManagers =
                从员工的context.Employees
                其中,employee.IsOverpaid和放大器;&安培; (employee.Manager == NULL)
                选择员工;
            VAR managersWithOverpaidSubordinates =
                从员工的context.Employees
                其中,employee.IsOverpaid和放大器;&安培; (employee.Manager!= NULL)
                选择employee.Manager;
            VAR的查询= overpaidTopManagers.Union(managersWithOverpaidSubordinates);

            //这将引发与消息NotSupportedException异常
            //,在欧盟或Concat的类型是不兼容的构建。
            的foreach(查询VAR经理)
            {
                Console.WriteLine(manager.ToString());
            }
        }
    }
}

[表]
内部密封类员工
{
    私人的EntityRef<员工>经理;

    [专栏(IsPrimaryKey = TRUE,IsDbGenerated =真)
    私人诠释编号;

    [柱]
    私人诠释?经理ID;

    [柱]
    内部布尔IsOverpaid;

    [公会(NAME =Manager_Subordinate,存储=经理,ThisKey =经理ID,IsForeignKey =真)
    内部员工管理
    {
        {返回this.manager.Entity; }
        集合{this.manager.Entity =价值; }
    }
}

内部密封类语境的DataContext
{
    内部表<员工>员工;

    内部上下文(字符串fileOrServerOrConnection):基地(fileOrServerOrConnection)
    {
        this.Employees = this.GetTable<员工>();
    }
}
 

解决方案

这个问题是关系到一个事实,即外键列允许为空。尝试更改经理ID列不允许使用空(只是一个无人占位符值指向添加到自己重新present的层次结构的根),然后再次尝试联盟,它现在应该工作。不要问我为什么,虽然,通过LINQ2SQL源$ C ​​$ C还是挖...

更新(preliminary答案,我的头的顶部): 正如我怀疑,除了必须做的事实,经理ID 可为空。唯一的例外文字有误导性:该错误不会发生,因为这两个查询的结果是不兼容的类型,但由于左侧和右侧的查询的内部重新presentations是不兼容的类型。 LINQ2SQL采取了不同的code路径时,它发现,该FK(即经理ID )可为空。有一个在你看到查询联接隐藏的,( employee.Manager ),如果经理ID 的类型是的Int32 然后LINQ2SQL知道它可以进行内连接。如果经理ID 是一个可为空INT然而,然后LINQ2SQL发现,它需要做左连接,即使在这个例子中,只要它能够与内部逃脱加入,因为筛选子句。

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

(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 ManagerId is 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 if ManagerId is of type Int32 then Linq2Sql knows that it can perform an inner join. If ManagerId is 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.