从LinqToXYZ切换到LinqToObjects

时间:2010-03-25 01:42:08

标签: c# linq

在回答this question时,它让我思考......

我经常使用这种模式:

collectionofsomestuff //here it's LinqToEntities
    .Select(something=>new{something.Name,something.SomeGuid})
    .ToArray() //From here on it's LinqToObjects
    .Select(s=>new SelectListItem()
        {
            Text = s.Name, 
            Value = s.SomeGuid.ToString(), 
            Selected = false
        })

也许我会把它拆分成两行,但基本上,在ToArray点,我实际上是在枚举我的查询并存储结果序列,这样我就可以进一步处理它所有的善良完整的CLR。

由于我对中间列表的任何操作都不感兴趣,我使用ToArray而不是ToList,因为开销较少。

我一直这样做,但我想知道这种问题是否有更好的模式?

2 个答案:

答案 0 :(得分:5)

如果你在LINQ查询的其余部分进行简单的赋值,那么Reed的答案确实是正确的。但是,如果您在查询的LinqToObjects部分中执行大量工作或计算,则如果考虑与基础数据源的连接,则他的解决方案会有一些小问题:

考虑:

collectionofsomestuff //here it's LinqToEntities
    .Select(something=>new{something.Name,something.SomeGuid})
    .AsEnumerable() //From here on it's LinqToObjects
    .Select(s=>new SelectListItem()
        {
            Text = s.Name, 
            Value = s.SomeGuid.ToString(), 
            OtherValue = someCrazyComputationOnS(s)
        })

如果您可以想象一下LinqToEntities选择函数的代码(高度简化,但您应该得到图片),它可能看起来像:

using(SqlConnection con = createConnection())
{
    using(SqlCommand com = con.CreateCommand())
   {
       con.Open();
       com.CommandText = createQuery(expression);

       using(SqlDataReader reader = com.ExecuteReader())
       {
           while(reader.Read())
           {
               yield return createClrObjectFromReader(reader);
           }
       }
   }
}

此方法支持传统的Linq延迟执行模式。这意味着无论何时从读取器读取结果,它都会“返回”给调用者,并且在调用者请求之前不会读取下一个值。

因此,在上面的代码中,5个记录的结果集的执行顺序是:

con.Open();

reader.Read();
createClrObjectFromReader(reader);
// at this point there is a yield back to the caller
someCrazyComputationOnS(s);


reader.Read();
createClrObjectFromReader(reader);
// at this point there is a yield back to the caller
someCrazyComputationOnS(s);


reader.Read();
createClrObjectFromReader(reader);
// at this point there is a yield back to the caller
someCrazyComputationOnS(s);


reader.Read();
createClrObjectFromReader(reader);
// at this point there is a yield back to the caller
someCrazyComputationOnS(s);


reader.Read();
createClrObjectFromReader(reader);
// at this point there is a yield back to the caller
someCrazyComputationOnS(s);

// ONLY here does the connection finally get closed:
con.Close();

虽然这确实保留了延迟执行模式。在这种情况下,这不是最佳选择。调用ToList()或ToArray()会导致整个原始查询结果被缓冲到数组或列表中,之后可以关闭SqlConnection。只有在关闭SqlConnection之后才会调用someCrazyComputationOnS(s)。

在大多数情况下,这不是一个问题,Reed的答案确实是正确的,但在极少数情况下,您正在对数据集进行大量工作,您肯定希望在继续进行大型LinqToObjects查询之前缓冲结果。

答案 1 :(得分:3)

有一个更好的选择:AsEnumerable

用法类似:

collectionofsomestuff //here it's LinqToEntities
    .Select(something=>new{something.Name,something.SomeGuid})
    .AsEnumerable() //From here on it's LinqToObjects
    .Select(s=>new SelectListItem()
        {
            Text = s.Name, 
            Value = s.SomeGuid.ToString(), 
            Selected = false
        })

但是,这并不会强制执行完整副本,如ToList()ToArray(),并保留您的提供商的任何延迟执行。