Tips to Get You Started

While working with LINQ to write this book, I often found myself confused, befuddled, and stuck. While there are many very useful resources available to the developer wanting to learn to use LINQ to its fullest potential, I want to offer a few tips to get you started. In some ways, these tips feel like they should come at the end of the book. After all, I haven't even explained what some of these concepts are at this point. But it would seem a bit sadistic to make you read the full text of the book first, only to offer the tips at the end. So with that said, this section contains some tips I think you might find useful, even if you do not fully understand them or the context.

Use the var Keyword When Confused

While it is necessary to use the var keyword when capturing a sequence of anonymous classes to a variable, sometimes it is a convenient way to get code to compile if you are confused. While I am very much in favor of developers knowing exactly what type of data is contained in a sequence—meaning that for IEnumerable<T> you should know what data type T is—sometimes, especially when just starting with LINQ, it can get confusing. If you find yourself stuck, where code will not compile because of some sort of data type mismatch, consider changing explicitly stated types so that they use the var keyword instead.

For example, let's say you have the following code:

//  This code will not compile.
Northwind db = new Northwind(@"Data Source=.SQLEXPRESS;Initial Catalog=Northwind");

IEnumerable<?> orders = db.Customers
  .Where(c => c.Country == "USA" && c.Region == "WA")
  .SelectMany(c => c.Orders);

It may be a little unclear what data type you have an IEnumerable sequence of. You know it is an IEnumerable of some type T, but what is T? A handy trick would be to assign the query results to a variable whose type is specified with the var keyword, then to get the type of that variable so you know what type T is. Listing 1-7 shows what the code would look like.

Example. Code Sample Using the var Keyword
Northwind db = new Northwind(@"Data Source=.SQLEXPRESS;Initial Catalog=Northwind");

var orders = db.Customers
  .Where(c => c.Country == "USA" && c.Region == "WA")
  .SelectMany(c => c.Orders);

Console.WriteLine(orders.GetType());

In this example, please notice that the orders variable type is now specified using the var keyword. Running this code produces the following:

System.Data.Linq.DataQuery`1[nwind.Order]

There is a lot of compiler gobbledygook there, but the important part is the nwind.Order portion. You now know that the data type you are getting a sequence of is nwind.Order.

If the gobbledygook is throwing you, running the example in the debugger and examining the orders variable in the Locals window reveals that the data type of orders is this:

System.Linq.IQueryable<nwind.Order> {System.Data.Linq.DataQuery<nwind.Order>}

This makes it clearer that you have a sequence of nwind.Order. Technically, you have an IQueryable<nwind.Order> here, but that can be assigned to an IEnumerable<nwind.Order> if you like, since IQueryable<T> inherits from IEnumerable<T>.

So you could rewrite the previous code, plus enumerate through the results, as shown in Listing 1-8.

Example. Sample Code from Listing 1-7 Except with Explicit Types
Northwind db = new Northwind(@"Data Source=.SQLEXPRESS;Initial Catalog=Northwind");

IEnumerable<Order> orders = db.Customers
  .Where(c => c.Country == "USA" && c.Region == "WA")
  .SelectMany(c => c.Orders);

foreach(Order item in orders)
  Console.WriteLine("{0} - {1} - {2}", item.OrderDate, item.OrderID, item.ShipName);

NOTE

For the previous code to work, you will need to have a using directive for the System.Collections.Generic namespace, in addition to the System.Linq namespace you should always expect to have when LINQ code is present.

This code would produce the following abbreviated results:

3/21/1997 12:00:00 AM - 10482 - Lazy K Kountry Store
5/22/1997 12:00:00 AM - 10545 - Lazy K Kountry Store
...
4/17/1998 12:00:00 AM - 11032 - White Clover Markets
5/1/1998 12:00:00 AM - 11066 - White Clover Markets

Use the Cast or OfType Operators for Legacy Collections

You will find that the majority of LINQ's Standard Query Operators can only be called on collections implementing the IEnumerable<T> interface. None of the legacy C# collections—those in the System.Collection namespace—implement IEnumerable<T>. So the question becomes, how do you use LINQ with legacy collections in your existing code base?

There are two Standard Query Operators specifically for this purpose, Cast and OfType. Both of these operators can be used to convert legacy collections to IEnumerable<T> sequences. Listing 1-9 shows an example.

Example. Converting a Legacy Collection to an IE<T> Using the Cast Operator
//  I'll build a legacy collection.
ArrayList arrayList = new ArrayList();
//  Sure wish I could use collection initialization here, but that
//  doesn't work with legacy collections.
arrayList.Add("Adams");
arrayList.Add("Arthur");
arrayList.Add("Buchanan");

IEnumerable<string> names = arrayList.Cast<string>().Where(n => n.Length < 7);
foreach(string name in names)
  Console.WriteLine(name);

Listing 1-10 shows the same example using the OfType operator.

Example. Using the OfType Operator
//  I'll build a legacy collection.
ArrayList arrayList = new ArrayList();
//  Sure wish I could use collection initialization here, but that
//  doesn't work with legacy collections.
arrayList.Add("Adams");
arrayList.Add("Arthur");
arrayList.Add("Buchanan");

IEnumerable<string> names = arrayList.OfType<string>().Where(n => n.Length < 7);
foreach(string name in names)
  Console.WriteLine(name);

Both examples provide the exact same results. Here they are:

Adams
Arthur

The difference between the two operators is that the Cast operator will attempt to cast every element in the collection to the specified type to be put into the output sequence. If there is a type in the collection that cannot be cast to the specified type, an exception will be thrown. The OfType operator will only attempt to put those elements that can be cast to the type specified into the output sequence.

Prefer the OfType Operator to the Cast Operator

One of the most important reasons why generics were added to C# was to give the language the ability to have data collections with static type checking. Prior to generics—barring creating your own specific collection type for every type of data for which you wanted a collection—there was no way to ensure that every element in a legacy collection, such as an ArrayList, Hashtable, and so on, was of the same and correct type. Nothing in the language prevented code from adding a Textbox object to an ArrayList meant to contain only Label objects.

With the introduction of generics in C# 2.0, C# developers now have a way to explicitly state that a collection can only contain elements of a specified type. While either the OfType or Cast operator may work for a legacy collection, Cast requires that every object in the collection be of the correct type, which is the fundamental original flaw in the legacy collections for which generics were created. When using the Cast operator, if any object is unable to be cast to the specified data type, an exception is thrown. Instead, use the OfType operator. With it, only objects of the specified type will be stored in the output IEnumerable<T> sequence, and no exception will be thrown. The best-case scenario is that every object will be of the correct type and be in the output sequence. The worst case is that some elements will get skipped, but they would have thrown an exception had the Cast operator been used instead.

Don't Assume a Query Is Bug-Free

In Chapter 3, I discuss that LINQ queries are often deferred and not actually executed when it appears you are calling them. For example, consider this code fragment from Listing 1-1:

var items =
  from s in greetings
  where s.EndsWith("LINQ")
  select s;

foreach (var item in items)
  Console.WriteLine(item);

While it appears the query is occurring when the items variable is being initialized, that is not the case. Because the Where and Select operators are deferred, the query is not actually being performed at that point. The query is merely being called, declared, or defined, but not performed. The query will actually take place the first time a result from it is needed. This is typically when the query results variable is enumerated. In this example, a result from the query is not needed until the foreach statement is executed. That is the point in time that the query will be performed. In this way, we say that the query is deferred.

It is often easy to forget that many of the query operators are deferred and will not execute until a sequence is enumerated. This means you could have an improperly written query that will throw an exception when the resulting sequence is eventually enumerated. That enumeration could take place far enough downstream that it is easily forgotten that a query may be the culprit.

Let's examine the code in Listing 1-11.

Example. Query with Intentional Exception Deferred Until Enumeration
string[] strings = { "one", "two", null, "three" };

Console.WriteLine("Before Where() is called.");
IEnumerable<string> ieStrings = strings.Where(s => s.Length == 3);
Console.WriteLine("After Where() is called.");

foreach(string s in ieStrings)
{
  Console.WriteLine("Processing " + s);
}

I know that the third element in the array of strings is a null, and I cannot call null.Length without throwing an exception. The execution steps over the line of code calling the query just fine. It is not until I enumerate the sequence ieStrings, and specifically the third element, that the exception occurs. Here are the results of this code:

Before Where() is called.
After Where() is called.
Processing one
Processing two

Unhandled Exception: System.NullReferenceException: Object reference not set to an
instance of an object.
...

As you can see, I called the Where operator without exception. It's not until I try to enumerate the third element of the sequence that an exception is thrown. Now imagine if that sequence, ieStrings, is passed to a function that downstream enumerates the sequence, perhaps to populate a drop-down list or some other control. It would be easy to think the exception is caused by a fault in that function, not the LINQ query itself.

Take Advantage of Deferred Queries

In Chapter 3, I go into deferred queries in more depth. However, I want to point out that if a query is a deferred query that ultimately returns an IEnumerable<T>, that IEnumerable<T> object can be enumerated over, time and time again, obtaining the latest data from the data source. You don't need to actually call or, as I earlier pointed out, declare the query again.

In most of the code samples in this book, you will see a query called and an IEnumerable<T> for some type T being returned and stored in a variable. Then I typically call foreach on the IEnumerable<T> sequence. This is for demonstration purposes. If that code is executed multiple times, calling the actual query each time is needless work. It might make more sense to have a query initialization method that gets called once for the lifetime of the scope and to construct all the queries there. Then you could enumerate over a particular sequence to get the latest version of the query results at will.

Use the DataContext Log

When working with LINQ to SQL, don't forget that the database class that is generated by SQLMetal inherits from System.Data.Linq.DataContext. This means that your generated DataContext class has some useful built-in functionality, such as a TextWriter object named Log.

One of the niceties of the Log object is that it will output the equivalent SQL statement of an IQueryable<T> query prior to the parameter substitution. Have you ever had code break in production that you think might be data related? Wouldn't it be nice if there was a way to get the query executed against the database, so that you could enter it in SQL Server Enterprise Manager or Query Analyzer and see the exact data coming back? The DataContext's Log object will output the SQL query for you. An example is shown in Listing 1-12.

Example. An Example Using the DataContext.Log Object
Northwind db = new Northwind(@"Data Source=.SQLEXPRESS;Initial Catalog=Northwind");

db.Log = Console.Out;

IQueryable<Order> orders = from c in db.Customers
                           from o in c.Orders
                           where c.Country == "USA" && c.Region == "WA"
                           select o;

foreach(Order item in orders)
  Console.WriteLine("{0} - {1} - {2}", item.OrderDate, item.OrderID, item.ShipName);

This code produces the following output:

SELECT [t1].[OrderID], [t1].[CustomerID], [t1].[EmployeeID], [t1].[OrderDate],
[t1].[RequiredDate], [t1].[ShippedDate], [t1].[ShipVia], [t1].[Freight],
[t1].[ShipName], [t1].[ShipAddress], [t1].[ShipCity], [t1].[ShipRegion],
[t1].[ShipPostalCode], [t1].[ShipCountry]
FROM [dbo].[Customers] AS [t0], [dbo].[Orders] AS [t1]
WHERE ([t0].[Country] = @p0) AND ([t0].[Region] = @p1) AND ([t1].[CustomerID] =
[t0].[CustomerID])
-- @p0: Input String (Size = 3; Prec = 0; Scale = 0) [USA]
-- @p1: Input String (Size = 2; Prec = 0; Scale = 0) [WA]
-- Context: SqlProvider(Sql2005) Model: AttributedMetaModel Build: 3.5.20706.1

3/21/1997 12:00:00 AM - 10482 - Lazy K Kountry Store
5/22/1997 12:00:00 AM - 10545 - Lazy K Kountry Store
6/19/1997 12:00:00 AM - 10574 - Trail's Head Gourmet Provisioners
6/23/1997 12:00:00 AM - 10577 - Trail's Head Gourmet Provisioners
1/8/1998 12:00:00 AM - 10822 - Trail's Head Gourmet Provisioners
7/31/1996 12:00:00 AM - 10269 - White Clover Markets
11/1/1996 12:00:00 AM - 10344 - White Clover Markets
3/10/1997 12:00:00 AM - 10469 - White Clover Markets
3/24/1997 12:00:00 AM - 10483 - White Clover Markets
4/11/1997 12:00:00 AM - 10504 - White Clover Markets
7/11/1997 12:00:00 AM - 10596 - White Clover Markets
10/6/1997 12:00:00 AM - 10693 - White Clover Markets
10/8/1997 12:00:00 AM - 10696 - White Clover Markets
10/30/1997 12:00:00 AM - 10723 - White Clover Markets
11/13/1997 12:00:00 AM - 10740 - White Clover Markets
1/30/1998 12:00:00 AM - 10861 - White Clover Markets
2/24/1998 12:00:00 AM - 10904 - White Clover Markets
4/17/1998 12:00:00 AM - 11032 - White Clover Markets
5/1/1998 12:00:00 AM - 11066 - White Clover Markets

Use the LINQ Forum

Despite providing the best tips I can think of, there will more than likely be times when you get stuck. Don't forget that there is a forum dedicated to LINQ at MSDN.com. You can find a link to it here: http://www.linqdev.com. This forum is monitored by Microsoft developers, and you will find a wealth of knowledgeable resources there.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset