C# 从LinqToXYZ切换到LINQTOO对象

C# 从LinqToXYZ切换到LINQTOO对象,c#,linq,C#,Linq,在回答时,它让我想到 我经常使用这种模式: collectionofsomestuff //here it's LinqToEntities .Select(something=>new{something.Name,something.SomeGuid}) .ToArray() //From here on it's LinqToObjects .Select(s=>new SelectListItem() { Tex

在回答时,它让我想到

我经常使用这种模式:

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
        })
也许我会将它分成几行,但实际上,在
toaray
点,我正在有效地枚举查询并存储结果序列,以便我可以进一步处理它,同时使用完整的CLR的优点

由于我对中间列表的任何类型的操作都不感兴趣,所以我使用
ToArray
而不是
ToList
,因为这样会减少开销


我一直在这样做,但我想知道是否有更好的模式来解决这类问题?

有一个更好的选择:

用法类似:

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()
,并保留提供者延迟执行的内容。

如果您在LINQ查询的其余部分执行简单赋值,则Reed的回答确实是正确的。但是,如果在查询的LIQOTObjts部分中做了重要的工作或计算,如果考虑到基础数据源的连接,他的解决方案会有一些小问题:

考虑:

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 select函数的代码(高度简化,但您应该能看到图片),它可能看起来像:

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关闭之后,才会真正发生对某些疯狂计算的调用


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

只是想快速评论一下,ToArray()和ToList()在这种情况下是等效的。打开reflector并查看Enumerable.ToArray(),您会发现它实现了一个内联数组列表,当它填满时,该列表将扩展2倍。这与List的行为相同,因此这两个选项或多或少是等效的。请记住,IEnumerable没有公开的计数信息,因此ToArray()方法需要一个arraylist风格的算法。我对此非常感兴趣,所以我打开了reflector以查看发生了什么。ToArray使用System.Linq.Buffer。在构造缓冲区时,似乎尝试将枚举强制转换为ICollection,如果转换成功,则使用Count属性优化数组分配。构造函数重载List(IEnumerable source)也使用了这个技巧。然而,在LinqToEntities查询的情况下,查询结果(几乎毫无疑问)是匿名IEnumerable类型,它不会实现ICollection。我记得看过Enumerable.Count()之类的东西,它们执行完全相同的测试。微软做测试是一个有趣的决定,当时它让我感觉不对劲,但现在我开始认为这是一个好主意……感谢你花时间来满足我的思考。我现在要上床睡觉了:)ToArray函数将使用ToList算法,但随后会将数据复制到大小完全正确的数组中。如果要让这些结果挂起一段时间,ToArray将更节省空间。只有在Linq查询的其余部分没有做大量工作时,ToArray才会更好。如果您正在做重要的工作,SqlConnection将在整个操作过程中保持打开状态。调用ToArray()或ToList()完成整个查询,并在此时关闭SqlConnection……我已经看到它一百万次了,但它几乎没有注册。很好的一个。那么在这种情况下,源流是否会有某种缓冲呢?或者我们真的会一次一个地得出结果吗?(我在这一层面上相当无知)我将为此写下另一个答案,这里没有足够的空间来解释……谢谢,延迟执行在某些地方还没有完全理解。。。我想指出延迟执行的最坏情况,只是为了确保人们在实施解决方案时牢记这一点……这一观点的另一面是,延迟处理可以让您轻松处理大量数据,而不会产生加载所有数据的成本。这可能非常方便。+1:但另一方面,如果你做了像Take(10)这样的事情,你真的在伤害自己……同意,你必须意识到各种事情都发生在.ToList()或.ToArray()的哪一边。Take(10)或类似命令应该在ToList()或ToArray()调用之前执行,并且将它们扔到另一边会造成严重的问题。。。还有一件事要考虑