2009-08-12 73 views
3

好,LINQ2SQL - 使用本地集合作为一个子查询的一部分 - “查询本地集合不支持”

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

这是相当难以描述没有给你们一个数据库图或代码图,但我会尽我所能。我试图执行我的代码与数据库的一个查询。我不想分解它并发送多个命令。这样做有一些优点,包括避免可能出现的问题,我会在最后解释这个问题。

我加入了一些有关系的表。属性(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的。选择(X => x.ID)后.ToList(),但副作用有两个底片。一,它首先运行该查询,并从数据库中获取ID ..然后它必须将这些结果作为参数传递给sql server作为参数传递给finalQuery。第二个主要的问题是,如果有很多来自.ToList()的结果,SQL服务器会抛出一些奇怪的错误信息,Google搜索显示有很多参数被传递(这是有道理的,因为参数数量可能在数千到数千)。所以说,我想弄清楚如何建立一个查询,我可以动态调整条件,然后重建我的结果集与所有符合符合条件的子查询的ID属性。在SQL服务器通过工作室,这工作正常,但收集问题让我抱着。

我已经尝试了很多不同的方法,但似乎重现此的唯一方法是有一个使用本地集合的查询,然后将该查询作为另一个查询的一部分使用第一个查询筛选结果。

任何想法,我可以做到这一点?

Screen shot show you know I'm not crazy.

在此先感谢您的帮助

回答

2

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 
         }; 
+0

Dahlbyk, 非常感谢。选项2是要走的路。 ([t5]。[ValueDecimal] = @ p1)OR([t5]。[ValueDecimal] = @ p2)OR([t5]。[ValueDecimal] = @ p3)OR([ t5]。[ValueDecimal] = @ p4)OR([t5]。[ValueDecimal] = @ p5))AND([t5]。[DataEventKeyID] = @ p6) 这正是我想要/需要的。 你在例子中显示的内容实际上延伸了我的思维过程,并且可能已经解决了我脑海中的其他想法。 非常感谢您的解决方案! – TravisWhidden 2009-08-13 15:06:51

0

我没有这方面的专家,但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

+0

的LINQ to SQL确实允许对本地收藏测试,但并非在每种情况。 – 2009-08-12 20:00:10

+0

谢谢丹。如果你只是将子IQueryable从查询中提取出来,并且自己运行它,这实际上可以工作。您可以根据Linq2Sql查询列表本地列表/集合,并将本地对象/集合转换为nice -in-statement。我本人试图找出如何使用select来填充“In”(又名包含在linq2sql中)的内容,而不具有值。在哪里(从某个表中选择someID来说明哪里)。达尔比克对谓词有一个好主意。但我会记住这一点。感谢您的反馈! – TravisWhidden 2009-08-13 15:02:44

+0

非常好,那是个好消息。我猜想一个简单的值类型列表,比如Ints可以用作SQL IN语句。我正在考虑更通用的对象集合,这显然不容易兑换。无论如何,学习一些东西很好,所以谢谢。 – 2009-08-13 20:05:17