返回包含2个子类的IQueryable

时间:2009-01-18 11:32:30

标签: c# linq-to-sql

你可以返回由两个或更多不同的子类组成的IQueryable吗?这是我尝试展示我的意思。它抛出错误:

System.NotSupportedException:中的类型 Union或Concat已分配成员 以不同的顺序..

var a = from oi in db.OrderItems
        where oi.OrderID == ID
            && oi.ListingID != null
        select new OrderItemA {
            // etc
        } as OrderItem;

var b = from oi in db.OrderItems
        where oi.OrderID == ID
            && oi.AdID != null
        select new OrderItemB {
            //etc
        } as OrderItem;

return a.Concat<OrderItem>(b);

4 个答案:

答案 0 :(得分:5)

尝试在IEnumerable而不是IQueryable上执行concat:

return a.AsEnumerable().Concat(b.AsEnumerable());

如果您需要IQueryable结果,您可以这样做:

return a.AsEnumerable().Concat(b.AsEnumerable()).AsQueryable();

执行此操作强制concat在内存中而不是在SQL中发生,并且任何其他操作也将在内存中发生(LINQ To Objects)。

但是,与.ToList()示例不同,执行仍应延迟(使数据延迟加载)。

答案 1 :(得分:2)

我的猜测是,这是因为您在LINQ-to-SQL上下文中使用LINQ。

因此使用Concat意味着LINQ2SQL需要将两个查询都连接到SQL UNION查询中,这可能是System.NotSupportedException源自的地方。

你可以试试这个:

return a.ToList().Concat<OrderItem>(b.ToList());

看看它是否有任何区别?

上面做的是它执行两次查询,然后在内存中连接它们而不是hot-off-SQL,以避免查询转换问题。

它可能不是理想的解决方案,但如果这项工作,我的假设可能是正确的,那就是查询翻译问题:

有关Union和Concat转换为SQL的更多信息:

希望这有帮助。

答案 2 :(得分:2)

有趣的是,在阅读了你的文章和一些测试之后,我意识到你实际做的事情对我来说似乎工作得很好,因为你在两个查询中显示为省略号的投影部分都匹配。你看,LINQ to SQL似乎构建了基于属性赋值语句的SQL select命令的底层投影,而不是实际的类型被实现,只要双方具有相同的数字,类型和顺序(不确定)关于此成员分配,UNION查询应该有效。

我一直在使用的解决方案是在我的DataContext类上创建一个属性,它的行为很像SQL视图,因为它允许我编写一个查询(在我的情况下是两个不同表之间的Union)然后在编写只读select语句时,使用该查询就好像它本身就像一个表。

public partial class MyDataContext
{
    public IQueryable<MyView> MyView
    {
        get
        {
            var query1 = 
                from a in TableA
                let default_ColumnFromB = (string)null
                select new MyView()
                {
                    ColumnFromA = a.ColumnFromA,
                    ColumnFromB = default_ColumnFromB,
                    ColumnSharedByAAndB = a.ColumnSharedByAAndB,
                };

            var query2 = 
                from a in TableB
                let default_ColumnFromA = (decimal?)null
                select new MyView()
                {
                    ColumnFromA = default_ColumnFromA,
                    ColumnFromB = b.ColumnFromB,
                    ColumnSharedByAAndB = b.ColumnSharedByAAndB,
                };

            return query1.Union(query2);
        }
    }
}

public class MyView
{
    public decimal? ColumnFromA { get; set; }
    public string ColumnFromB { get; set; }
    public int ColumnSharedByAAndB { get; set; }
}

注意两件关键事项:

首先,由构成联盟两半的查询形成的投影具有相同的数量,类型和列的顺序。现在LINQ可能要求命令是相同的(不确定)但是对于UNION来说肯定是正确的,我们可以确定LINQ至少需要相同类型和数量的列以及这些“列”成员 assignments 已知,而不是您在投影中实例化的类型的属性。

其次,LINQ目前不允许在构成Concat或Union的查询的投影中使用多个常量,根据我的理解,这主要是因为这两个单独的查询在处理Union操作之前被单独优化。通常,LINQ to SQL足够聪明,可以意识到如果你有一个仅在投影中使用的常量值,那么为什么要将它发送到SQL只是为了让它以它的方式回来,而不是将其作为帖子添加原始数据从SQL Server返回后的进程。不幸的是,这里的问题是这是LINQ to SQL为了自己的利益而智能化的一个例子,因为它在过程中过早地优化了每个单独的查询。我发现解决这个问题的方法是使用 let 关键字为投影中的每个值形成一个范围变量,通过从常量中获取它的值来实现。不知何故,这使得LINQ to SQL将这些常量传递给实际的SQL命令,该命令将所有预期的列保留在生成的UNION中。有关此技术的更多信息,请参见here

使用这个技术我至少有一些可重用的东西,所以无论实际的Union有多复杂或丑陋,特别是对于范围变量,在最终查询中你可以向这些伪视图(如MyView和交易)写入查询具有复杂性。

答案 3 :(得分:1)

你可以在 concat之后做投影吗?

// construct the query
var a = from oi in db.OrderItems
        where oi.OrderID == ID
            && oi.ListingID != null
        select new {
            type = "A"
            item = oi
        }

var b = from oi in db.OrderItems
        where oi.OrderID == ID
            && oi.AdID != null
        select new {
            type = "B"
            item = oi
        }

var temp = a.Concat<OrderItem>(b);

// create concrete types after concatenation
// to avoid inheritance issue

var result = from oi in temp
             select (oi.type == "A"
                 ? (new OrderItemA {
                         // OrderItemA projection

                     } as OrderItem)

                 : (new OrderItemB {
                         // OrderItemB projection

                     } as OrderItem)
             );

return result

在上述场景中,不确定三元运算符是否在LINQ2SQL中工作,但这可能有助于避免继承问题。