玩转泛型集合
玩转用于查询集合的各种接口。
引言
本文仅触及了在使用 ORM(如 Entity Framework)访问数据时,何时以及如何使用不同集合接口的表面。
背景
在处理从数据库检索的数据列表时,很容易习惯仅依赖 List<T>
。但了解数据可以从方法中“生成”以及从调用代码中“消耗”数据的不同方式很有趣。
让我们开始……
这背后的概念非常美妙……
以使用 Entity Framework 等 ORM 进行基本数据访问为例:
public IEnumerable<dCustomer> GetAllCustomers() { IEnumerable<dCustomer> cResults = null; using (MyTest1DBConnection context = new MyTest1DBConnection()) { cResults = (from q in context.dCustomers select q); } // cResults is of type ObjectQuery! return cResults; }
这如何工作?
请记住,cResults
的类型是 ObjectQuery<dCustomers>
,而 ObjectQuery
类同时实现了 IEnumerable
和 IQueryable
接口。这意味着我们可以将方法签名更改为
public IQueryable<dCustomer> GetAllCustomers()
这样,它仍然可以编译。
何时为返回类型使用 IEnumerable、IQuerable、List?
实现 IEnumerable
接口使我们的 ObjectQuery
对象能够以仅前向(forward-only)的方式读取结果,而 IQueryable
则允许我们执行类似 .Where(...) 的操作,并在查询实际执行之前“修改”它。
- Enumerable<T> 属于
System.Collections
命名空间。它只有一个GetEnumerator()
方法,该方法返回一个Enumerator<T>
,您可以在其上调用MoveNext()
方法来遍历T
的序列…… - IQueryable<T> 属于
System.Linq
命名空间,并实现IEnumerable<T>
,但添加了查询提供程序和表达式引用。它允许我们执行 .Where() 等操作并修改我们的查询。 - ICollection<T> 允许添加和删除条目,并在拥有实际数据集合时使用。
- List<T> 是
IEnumerable
的实际具体实现,但不是IQueryable
,并且可以通过调用 .ToList() 方法从实现这两个接口之一的类生成。 List<T>
从何处获得执行 .Where() 类型操作的能力,因为它不实现IQueryable
?因为它实现了ICollection
接口,该接口具有这些方法。
请注意!
我们还没有执行查询,也无法访问结果。我们得到“某个东西”(ObjectQuery
类),它具有获取数据的方法,但还没有数据本身。
尝试访问数据将引发“ObjectContext 实例已被释放,无法再用于需要连接的操作。”错误。
要访问数据,我们必须在离开数据上下文范围之前获取数据。这可以通过多种方式完成:
1. 将cResults
声明为 IEnumerable<dCustomer>
类型:cResults = (from q in context.dCustomers select q); IEnumerator<dCustomer> enumerator = cResults.GetEnumerator(); while (enumerator.MoveNext()) { string strContent = Convert.ToString(enumerator.Current.Name); // Add it to a list, array or any structure implementing the IEnumerable interface. }
2. 将 cResults
声明为 ICollection<dCustomer>
或 List<dCustomer>
类型
cResults = (from q in context.dCustomers select q).ToList();
请记住,如果我们希望数据在方法外部可访问,我们的方法必须返回任何已填充数据的列表结构。这意味着列表、数组、hashset……或者更具体地说,任何使用“ICollection
”接口的东西。我们可以将方法签名更改为
public ICollection<dCustomer> GetAllCustomers()
这样,它仍然可以工作。
现在是关于有趣的部分
我们现在有一个方法,它可以作为列表、数组、hashset……也就是说:多种形式……返回数据,而无需显式指定类型。这是因为我们的方法返回类型是 IEnumerable
或 ICollection
之一。
在接收端,我们可以将结果转换为我们想要的任何其他类型,只要它也实现 IEnumerable 或 ICollection。
如果我们从代码库的其他地方调用该方法
MyTest1Data.CustomerManager cmData = new MyTest1Data.CustomerManager(); List<dCustomer> lResults = cmData.GetAllCustomers().ToList(); //or dCustomer[] lResults = cmData.GetAllCustomers().ToArray();
这意味着我们可以返回一个对象数组,然后在接收端调用 .ToList()
来得到一个列表。或者我们可以在方法内部返回一个 List,让接收端将其用作 array[]
。所有这些都在接收端不必知道我们方法内部生成数据的具体情况。
这也意味着如果我们“发送”的数据是 List,并且在“接收”数据时将其作为 List,我们可能需要调用两次 .ToList()
(一次在方法内部,然后再次在接收端),因为接收端只知道它是一个实现了 IEnumerable
接口的“某物”。
关注点
以下链接也可能有用:
- http://www.dotnet-tricks.com/Tutorial/linq/I8SY160612-IEnumerable-VS-IQueryable.html.
- http://stackoverflow.com/questions/252785/what-is-the-difference-between-iqueryablet-and-ienumerablet
历史
- First version.