【问题标题】:Linq2Sql - Using a local collection as part of a sub query - "queries with local collections are not supported"Linq2Sql - 使用本地集合作为子查询的一部分 - “不支持使用本地集合的查询”
【发布时间】:2009-08-27 21:31:06
【问题描述】:

好的,

我上次发布这个(上周)时,我没有正确描述问题。我已经创建了这个问题的快速示例。查询本地集合可以很好地使用它作为基本查询的一部分。我发现的问题是将它与子查询的一部分一起使用。例如。

如果不给你们一个数据库图或代码图,这很难描述,但我会尽力而为。我正在尝试通过对数据库的一个查询来执行我的代码。我不想分解它并发送多个命令。这样做有一些好处,包括避免可能出现的问题,我将在最后解释。

我正在加入一些有关系的表。属性 (DataEventAttributes) 表当然描述了主表 (DataEvents) 中特定行所特有的属性。

当我在没有任何本地集合的情况下查询它时,对我的 20 gig 数据库来说,事情工作得很好并且非常快。但是,如果我将本地值集合扔到获取结果的子查询的一部分中,我将得到“不支持具有本地集合的查询”

这对我来说很难在我的代码中重现,所以我会尽我所能评论它,你可以按照我在做什么。

// gets the initial query and join. We actually only care about the ID in the end, but we use the joined data
        // to determine if a row needs to be pulled.
        var initialQuery = from dataEvent in DataEvent.GetByQueryExpression(context)
                                  join attribute in DataEventAttribute.GetByQueryExpression(context) on dataEvent.DataEventID
                                      equals attribute.DataEventID
                           select new
                           {
                               ID = dataEvent.DataEventID,
                               PluginID = dataEvent.DataOwnerID,
                               TimeStamp = dataEvent.DataTimeStamp,
                               DataEventKeyID = attribute.DataEventKeyID,
                               ValueString = attribute.ValueString,
                               ValueDecimal = attribute.ValueDecimal
                           };

        // list of some ids that we need to confirm exist in the initial query before the final query
        var someSetOfIDs = new List<int>() {1, 2, 3, 4, 5};

        // This is the local collection thats filtering out some results before I rebuild the entire result set in the final query
        // If you comment this line out, the finalQuery will execute just fine.
        // with this in place, the "Queries with local collections are not supported" error will come about.
        initialQuery = initialQuery.Where(x => x.DataEventKeyID == 1 && someSetOfIDs.Contains((int) x.ValueDecimal));

        // reusable query for the sub queries in the results -- not part of the problem, just part of the example
        var attributeBaseQuery = from attribute in DataEventAttribute.GetByQueryExpression(context) select attribute;

        // Builds the final result With the IDs from the initial query 
        // the group by is to remove any duplicates that may be in the collection.
        // the select key is getting the ID that i needed
        // the select ID is the ID of the first item that was grouped.
        // the contains compares the local dataEvent object with the ID table (checking to see if it exists)
        // the result is just an example of one item I can be pulling out of the database with the new type
        var finalQuery = from dataEvent in DataEvent.GetByQueryExpression(context)
                         where initialQuery.GroupBy(x => x).Select(x => x.Key).Select(x => x.ID).Contains(dataEvent.DataEventID)
                         select new
                                    {
                                        BasicData =
                                         attributeBaseQuery.Where(
                                         attrValue =>
                                         attrValue.DataEventID == dataEvent.DataEventID &&
                                         attrValue.DataEventKeyID == (short) DataEventTypesEnum.BasicData).FirstOrDefault().
                                         ValueString
                                    };

        var finalResult = finalQuery.Take(100).ToList();

我发现的一个解决方案是在 finalQuery 中的 .Select(x => x.ID) 之后执行 .ToList(),但副作用有两个负面影响。第一,它首先运行该查询,并从数据库中获取 ID。然后它必须将这些结果作为参数传递回 sql 服务器作为 finalQuery。第二个主要(显示停止)是,如果 .ToList() 有很多结果,SQL 服务器会抛出一些奇怪的错误消息,并且 Google 搜索显示有很多参数被传递(这是有道理的,因为参数计数可能在 10 到 100 之间)。

也就是说,我试图弄清楚如何构建一个可以动态调整条件的查询,然后使用与满足子查询条件的 ID 匹配的所有属性重建我的结果集。通过工作室在 SQL Server 中,这工作正常,但收集问题让我陷入困境。

我尝试了许多不同的方法,但似乎重现此问题的唯一方法是使用本地集合的查询,然后将该查询用作另一个查询的一部分,该查询使用第一个查询过滤结果。

有什么想法可以做到这一点吗?

Screen shot show you know I'm not crazy.

提前感谢您的帮助

【问题讨论】:

    标签: c# linq-to-sql


    【解决方案1】:

    AFAIK,无法在 LINQ to SQL 查询中使用内存中的集合。我可以想到两种可能的解决方法:

    选项 1:对每个 ID 执行查询:

        var someSetOfIDs = new List<int>() {1, 2, 3, 4, 5};
    
        // queryPerID will have type IEnumerable<IQueryable<'a>>
        var queryPerID = from id in someSetOfIDs
                         select (
                           from dataEvent in DataEvent.GetByQueryExpression(context)
                           join attribute in DataEventAttribute.GetByQueryExpression(context)
                             on dataEvent.DataEventID
                                      equals attribute.DataEventID
                           where attribute.DataEventKeyID == 1
                                   && (int)attribute.ValueDecimal == id // Changed from Contains
                           select new
                           {
                               ID = dataEvent.DataEventID,
                               PluginID = dataEvent.DataOwnerID,
                               TimeStamp = dataEvent.DataTimeStamp,
                               DataEventKeyID = attribute.DataEventKeyID,
                               ValueString = attribute.ValueString,
                               ValueDecimal = attribute.ValueDecimal
                           });
    
        // For each of those queries, we an equivalent final queryable
        var res = from initialQuery in queryPerID
                  select (
                      from dataEvent in DataEvent.GetByQueryExpression(context)
                      where initialQuery.GroupBy(x => x).Select(x => x.Key.ID).Contains(dataEvent.DataEventID)
                      select new
                      {
                          BasicData =
                              attributeBaseQuery.Where(
                              attrValue =>
                                  attrValue.DataEventID == dataEvent.DataEventID &&
                                  attrValue.DataEventKeyID == (short) DataEventTypesEnum.BasicData).FirstOrDefault().
                                  ValueString
                      }) into finalQuery
                  from x in finalQuery
                  select x;
    
        var finalResult = finalQuery.Take(100).ToList();
    

    我不确定这是否可以编译,但应该非常接近。

    选项 2:从 someSetOfIDs 构建谓词表达式以传递给 SQL。

            var someSetOfIDs = new List<decimal>() { 1, 2, 3, 4, 5 };
    
            Expression<Func<DataEventAttribute, bool>> seed = x => false;
            var predicate = someSetOfIDs.Aggregate(seed,
                (e, i) => Expression.Lambda<Func<DataEventAttribute, bool>>(
                    Expression.OrElse(
                        Expression.Equal(
                            Expression.Property(
                                e.Parameters[0],
                                "ValueDecimal"),
                            Expression.Constant(i)),
                        e.Body),
                    e.Parameters));
    

    基本上我们已经构建了一个 where 子句:

    x => ((x.ValueDecimal = 5) || ((x.ValueDecimal = 4) || ((x.ValueDecimal = 3) ||
    ((x.ValueDecimal = 2) || ((x.ValueDecimal = 1) || False)))))
    

    请务必注意,这种方法不适用于匿名类型,因此您必须在具有命名类型的可查询对象上使用谓词。如果您重新组织一下(实际上可能会产生更好的查询计划),这不是问题:

        var attributes = DataEventAttribute.GetByQueryExpression(context)
                         .Where(a => a.DataEventKeyID ==1)
                         .Where(predicate);
    
        var initialQuery = from dataEvent in DataEvent.GetByQueryExpression(context)
                           join attribute in attributes
                           select new
                           {
                               ID = dataEvent.DataEventID,
                               PluginID = dataEvent.DataOwnerID,
                               TimeStamp = dataEvent.DataTimeStamp,
                               DataEventKeyID = attribute.DataEventKeyID,
                               ValueString = attribute.ValueString,
                               ValueDecimal = attribute.ValueDecimal
                           };
    

    【讨论】:

    • 达尔比克,非常感谢。选项2是要走的路。得到的 SQ1 是: WHERE (([t5].[ValueDecimal] = @p1) OR ([t5].[ValueDecimal] = @p2) OR ([t5].[ValueDecimal] = @p3) OR ([t5] .[ValueDecimal] = @p4) OR ([t5].[ValueDecimal] = @p5)) AND ([t5].[DataEventKeyID] = @p6) 这正是我想要/需要的。您在示例中展示的内容实际上扩展了我的思维过程,并且可能解决了我脑海中的其他想法。非常感谢您的解决方案!
    【解决方案2】:

    我不是这方面的专家,但 LinqToSql 的工作原理是构建一个表达式树,该树在执行时转换为 SQL 查询。如果您的所有查询都可以转换为 SQL,则此方法可以正常工作。但是,您所做的基本上是尝试将您的 SQL 查询与 .NET 对象集合连接起来。问题是,这行不通,因为无法将连接转换为 SQL 查询。您正在混合两种不同的东西 - LinqToSql 和 LinqToObjects。在您的 LinqToSql 上调用 ToList() 使其能够像您回到 LinqToObjects 域一样工作。抱歉,恐怕我不知道有什么办法解决这个问题。

    PS。也许看到这个问题:Linq2Sql -> Searching the database against a local collection of values - Queries with local collections are not supported

    【讨论】:

    • LINQ to SQL 允许针对本地集合进行测试,但并非在所有情况下都可以。
    • 谢谢丹。如果您只是将子 IQueryable 从查询中拉出并自行运行,这实际上确实有效。您可以针对 Linq2Sql 查询一个本地列表/集合列表,并将本地对象/集合转换为一个不错的 -in- 语句。我自己,我试图找出如何在没有值的情况下使用选择填充“In”(又名包含在 linq2sql 中)的内容。在哪里(从一些表中选择一些ID)。 dahlbyk 对谓词有一个好主意。我会记住这一点。感谢您的反馈!
    • 非常好,这是个好消息。我猜想一个简单的值类型列表,例如 Ints 可以用作 SQL IN 语句。我正在考虑更通用的对象集合,这些对象显然不容易转换。无论如何,学习一些东西很好,所以谢谢。
    猜你喜欢
    • 1970-01-01
    • 2011-05-23
    • 1970-01-01
    • 2020-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多