Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / ASP.NET

Fun with Generic Collections

4.67/5 (6 votes)
7 Nov 2013CPOL3 min read 12.9K  
Play around with the different interfaces used to query collections.

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);
   }

   // cResults is of type ObjectQuery!   
   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:

C#
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);
    // Add it to a list, array or any structure implementing the IEnumerable interface.
}  

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();
//or
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

  • First version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)