Introduction
This article just touch the surface of when and how to use the different collection interfaces when using a ORM (like the Entity Framework) to access data.
Background
It's easy to get used to just relying on List<T>
when working with a list of data retrieved from the database. But it's interesting to see the different ways in which the data can be "generated" from a method, and "consumed" from the calling code.
Let's start ..
The concepts behind this is just beautiful ..
Consider an example when doing basic data access using an ORM like the Entity Framework:
public IEnumerable<dCustomer> GetAllCustomers()
{
IEnumerable<dCustomer> cResults = null;
using (MyTest1DBConnection context = new MyTest1DBConnection())
{
cResults = (from q in context.dCustomers
select q);
}
return cResults;
}
How does this work?
Remember that cResults
is of
type ObjectQuery<dCustomers>
and the ObjectQuery
class implements both the
IEnumerable
and IQueryable
interfaces. That means we could have changed our method signature to be:
public IQueryable<dCustomer> GetAllCustomers()
instead and it still would've compiled.
When to use IEnumerable, IQuerable, List for the return type?
Having implemented the
IEnumerable
interface allows our ObjectQuery
object the ability to read the results in a forward-only manner, while the
IQueryable
allows us to do things like .Where(...) and "modify" the query before it's actually executed.
- Enumerable<T> is part of the
System.Collections
namespace. I only have a GetEnumerator()
method that returns an Enumerator<T>
on which you can call its
MoveNext()
method to iterate through a sequence of T
.. - IQueryable<T> is part of the
System.Linq
namespace and implements IEnumerable<T>
but with a query provider and expression references added. It allows us to do things like .Where() and modify our query. - ICollection<T> allows to add and remove entries and is used when having an actual collection of data.
- List<T> is an actual concrete implementation of
IEnumerable
but not
IQueryable
and can be generated from a class implementing any of those two interfaces by calling the .ToList() method. - Where does
List<T>
get the ability to perform .Where() kind of operations, since it doesn't implement
IQueryable
? Because it implements the ICollection
interface which have those methods.
Watch out!
We haven't executed the query and can't access the results yet. We got 'something' (the
ObjectQuery
class) with methods to get the data, but not the data itself yet.
Trying to access the data would throw the "The ObjectContext instance has been disposed and can no longer be used for operations that require a connection." error.
To access the data we have to get the data before leaving the scope of the data context. This can be done in many ways:
1. with
cResults
being declared as of type
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);
}
2. with cResults
being declared as of type ICollection<dCustomer>
or
List<dCustomer>
:
cResults = (from q in context.dCustomers
select q).ToList();
Remember that if we want the data to be accessible outside of our method, our method must return any list structure populated with data. That means a list, array, hashset .. or more specifically anything using the 'ICollection
' interface. We can change our method signature to be
public ICollection<dCustomer> GetAllCustomers()
instead and it will still work.
Now for the interesting part:
We now have a method that can return the data as a list, an array, a hashset .. in other words: multiple forms .. without specifying the type explicitly. This is because our method return type is of either
IEnumerable
or ICollection
.
On the receiving side, we can cast the results as anything else we want so long as it's also implementing IEnumerable or ICollection.
If we call the method from elsewhere in our codebase:
MyTest1Data.CustomerManager cmData = new MyTest1Data.CustomerManager();
List<dCustomer> lResults = cmData.GetAllCustomers().ToList();
dCustomer[] lResults = cmData.GetAllCustomers().ToArray();
That means we can return an array of objects, and just call
.ToList()
on the receiving end to end up with a list instead. Or we can return a List inside the method and have the receiving end use it as an
array[]
.
All the while without the receiving end having to know what's going on inside our method generating the data.
It also mean we might have to call .ToList()
twice if we "send" the data out as a List and "receive" the data as a List (once inside
the method, and then again on the receiving end) since the receiving side only knows that it is a "something" implementing the
IEnumerable
interface.
Points of Interest
The following links could also prove useful:
History