你可以返回由两个或更多不同的子类组成的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);
答案 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中工作,但这可能有助于避免继承问题。