【问题标题】:Return IQueryable containing 2 Subclasses返回包含 2 个子类的 IQueryable
【发布时间】:2009-01-18 11:32:29
【问题描述】:

你能返回由两个或多个不同子类组成的 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);

【问题讨论】:

    标签: c# linq-to-sql


    【解决方案1】:

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

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

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

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

    这样做强制连接发生在内存中而不是 SQL 中,并且任何其他操作也将发生在内存中(LINQ To Objects)。

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

    【讨论】:

    • 实际上,调用 a.AsEnumerable() 就足够了,强制编译器选择 Enumerable.Concat,它会将 b 视为 IEnumerable。转换为 IQueryable 可能不会有任何好处,因为表达式只会被编译(性能下降)并针对连接的 IEnumerable 在内存中运行。
    【解决方案2】:

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

    因此,使用 Concat 意味着 LINQ2SQL 需要将两个查询加入到 SQL UNION 查询中,这可能是 System.NotSupportedException 的来源。

    你能试试这个吗:

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

    看看会不会有什么不同?

    上面的做法是执行两次查询,然后在内存中连接它们而不是热关闭SQL,以避免查询翻译问题。

    这可能不是理想的解决方案,但如果可行,我的假设可能是正确的,这是一个查询翻译问题:

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

    希望这会有所帮助。

    【讨论】:

    • 感谢您的帮助。我不想调用 ToList,因为我在延迟加载场景中使用它,并且 ToList 提供了数千个查询。
    【解决方案3】:

    有趣的是,在阅读了您的帖子并进行了一些测试后,我意识到您的实际操作对我来说似乎工作得很好,因为您在两个查询中显示为省略号的投影部分都匹配。您会看到,LINQ to SQL 似乎是根据属性赋值语句构造 SQL 选择命令的基础投影,而不是物化的实际类型,只要双方具有相同的编号、类型和顺序(不确定关于这个)的成员分配 UNION 查询应该是有效的。

    我一直在使用的解决方案是在我的 DataContext 类上创建一个属性,该属性的作用很像 SQL 视图,因为它允许我编写查询(在我的情况下是两个不同表之间的联合),然后在编写只读选择语句时,将该查询当作一个表来使用。

    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 可能要求顺序相同(对此不确定),但 SQL 确实适用于 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 和 deal与下面的复杂性。

    【讨论】:

      【解决方案4】:

      你能 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,但这可能有助于避免继承问题。

      【讨论】:

      • 好主意,但最终会出现同样的错误。您不能使用三元运算符,因为“无法确定条件表达式的类型,因为 'OrderItemA' 和 'OrderItemB' 之间没有隐式转换”
      • 您是否还要求结果为 IQeuryable 形式?
      • 感谢您的努力。这也不起作用,我得到了惊人的异常: System.Security.VerificationException: Operation could destabilize the runtime.. 我想我们刚刚破坏了 .net :)
      • 哇...我们把它弄坏了!哈哈哈.. 无论如何,Linq2Sql 有继承问题,所以你可能想从一开始就解决继承问题。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-07-03
      • 2023-04-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多