Addendums to Scott Hanselman's postFellow RD Scott Hanselman wrote his thoughts on Good Exception Management Rules of Thumb here.
I agree with everything he said, and I'll add a few points. By the way, to get even more about best practices and exceptions, you should read Chapter 7 of Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries, by Cwalina & Abrams.
Scott says:
If your functions are named well, using verbs (actions) and nouns (stuff to take action on) then throw an exception if your method can't do what it says it can.
For example, SaveBook(). If it can't save the book - it can't do what it promised - then throw an exception. That might be for a number of reasons.
This is the core guidance for the framework design: A method fulfills its contract, or it throws. Period.
Scott sez:
If you can, throw an exception that means something, and if there's an exception that already exists that matches what happened semantically, throw that.
Don't create a HanselmanException just because you're writing the Hanselman module unless you're adding data or valuable semantics to the type.
My addendum: First of all, you should always throw exceptions that mean something. One of the key benefits to using exceptions to report errors is that they can carry rich semantic information about the cause of the error. Throwing meaningless exceptions nullifies that benefit. Regarding the creation of new exceptions, the reason to create a new exception type is that the caller may want to differentiate that type of error in a catch block. For example, SaveBook() might fail because its state is invalid, or the persistent storage is unavailable. The recovery process is different, therefore the exception types should be different. In the case of an unavailable storage medium, there already exists a good exception. In the case of an invalid internal state, a new type might be warranted.
Scott sez:
If something horrible happens (something exceptional) then you need to decide if you can keep going.
Don't catch exceptions you can't do anything about. It's likely if you could do something about it, it wouldn't be exceptional, and you might consider calling TryParse, or File.Exists, or whatever it takes to prevent that exception.
My addendum: Three points here: 1) File.Exists and its kin are referred to as the Tester-Doer pattern. And, while it’s often a good idea, it can introduce race conditions: State can change between the Test and the Do. For File.Exists, it’s probably unlikely, but if you implement it yourself, be aware of that issue. 2) TryParse() and it’s related methods, such as Dictionary.TryGetValue, solve both the exception problem and the race condition problem associated with the Tester-Doer pattern. 3) I’ve found that pointing people at the tester-doer pattern and the try pattern makes it much more clear when to provide your own implementation of the tester-doer pattern, or the Try-Parse pattern: SomeDictionary.GetItem(…) throws an exception if it can’t return an item. But, provide a SomeDictionary.TryGetItem() as well.
Scott's full postI doubt I'd have said it better
Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET LibrariesThe reference for .NET best practices
How the LinqToSql libraries translates query expressions into T-SQLThis blog entry will finish the Where category of DLinq samples. Most of the logic is familiar, if you’ve read my earlier series on the core LINQ language enhancements. However, there are some interesting twists on the way query language works when you are pulling data from a SQL database. But, you’ll only care if you are trying to round-trip the data, modifying it and storing it back in the database.
The first sample for today shows how First works with Linq to SQL:
public void DLinq6() {
Shipper shipper = db.Shippers.First();
ObjectDumper.Write(shipper, 0);Just like we saw in the core LINQ libraries, First returns the first item in the collection. But, let’s walk through the code. The ‘db’ variable above is a member variable in the SampleQueries class:
private Northwind db;
The Northwind class is a specific class that maps objects from your object model to the Northwind (sample) database. Here’s a small portion:
public partial class Northwind : DataContext {
// snip
public Table<Shipper> Shippers;
// snip
}Note that the Northwind class is derived from DataContext. The DataContext class is part of the core Linq to SQL library, and is responsible for doing all the magic of mapping the in-memory objects you use and the persistent model in the database. It’s mostly done using attributes with some reasonable defaults. We’ll get to exactly how it works later, but for now, realize that the Northwind class maps to a database, and the Table generic class represents one table in that database. And, if you pull a record from a database, the Table and DataContext classes ‘remember’ it, so that you can save updates back to the database easily.
Ok, but back to this sample. It pulls the first record from the Shipper table. The output shows the SQL query, and its output:
SELECT TOP 1 [t0].[ShipperID], [t0].[CompanyName], [t0].[Phone]
FROM [Shippers] AS [t0]
ShipperID=1 CompanyName=Speedy Express
Phone=(503) 555-9831 Orders=...
Look back at the code, and remember that the result of the query is stored in a Shipper object. That’s good, because LinqToSQL does remember which record you retrieved, and if you modify it, you can store it back into the correct location of the database.
You can also look for the first element that matches a condition:
public void DLinq7() {
Customer cust = db.Customers.First(c => c.CustomerID == "BONAP");
ObjectDumper.Write(cust, 0);
}This uses the following query to retrieve the first (and probably only) customer that has the customer ID of “BONAP”: Once again, the query stores the result in a Customer object, so LinqToSQL can track its reference to the database.
SELECT TOP 1 [t0].[CustomerID], [t0].[CompanyName], [t0].[ContactName],
[t0].[ContactTitle], [t0].[Address], [t0].[City], [t0].[Region],
[t0].[PostalCode], [t0].[Country], [t0].[Phone], [t0].[Fax]
FROM [Customers] AS [t0]
WHERE [t0].[CustomerID] = @p0
CustomerID=BONAP CompanyName=Bon app' ContactName=Laurence Lebihan
ContactTitle=Owner Address=12, rue des Bouchers
City=Marseille Region=null PostalCode=13008
Country=France Phone=91.24.45.40 Fax=91.24.45.41
CustomerCustomerDemos=... Orders=...
You can also find an element that matches any other arbitrary condition:
public void DLinq8() {
Order ord = db.Orders.First(o => o.Freight > 10.00M);
ObjectDumper.Write(ord, 0);
}The output is below. Notice that the value of freight is parameterized in the generated query. And, it’s hard to avoid emphasizing this again: The result of the query is stored in an object that knows about the database model.
SELECT TOP 1 [t0].[OrderID], [t0].[CustomerID], [t0].[EmployeeID], [t0].[OrderDate],
[t0].[RequiredDate], [t0].[ShippedDate], [t0].[ShipVia], [t0].[Freight], [t0].[ShipName],
[t0].[ShipAddress], [t0].[ShipCity], [t0].[ShipRegion],
[t0].[ShipPostalCode], [t0].[ShipCountry]
FROM [Orders] AS [t0]
WHERE [t0].[Freight] > @p0
OrderID=10248 CustomerID=VINET EmployeeID=5 OrderDate=7/4/1996
RequiredDate=8/1/1996 ShippedDate=7/16/1996 ShipVia=3 Freight=32.3800
ShipName=Vins et alcools Chevalier ShipAddress=59 rue de l'Abbaye ShipCity=Reims
ShipRegion=null ShipPostalCode=51100 ShipCountry=France
OrderDetails=... Customer={ } Employee={ } Shipper={ }
OK. Now it’s time for a brief break to discuss why I’ve been hammering home the point of storing the result of a query in an object of a type that understands your database model. Remember from my earlier posts on the core LINQ libraries that LINQ supports projects, extractions, and anonymous types. Those are really cool, but you need to view them as read-only objects when you’re working with LinqToSql. That’s because when you extract individual fields, or project fields into a new anonymous type, LinqToSql doesn’t retain the knowledge about where it came from in the database. Don’t complain about, because it’s probably an intractable problem. Instead, understand that you should not use some of the Linq features demonstrated below if you intend to modify the records and put them back in the database.
Just like core Linq, you can extract just the name field from a database table:
var q =
from c in db.Customers
select c.ContactName;
Which generates this query:
SELECT [t0].[ContactName]
FROM [Customers] AS [t0]
I don’t need to show the output, do I? Good.
You can extract new anonymous types form a portion of a database table:
var q =
from c in db.Customers
select new {c.ContactName, c.Phone};
Which of course generates this query:
SELECT [t0].[ContactName], [t0].[Phone]
FROM [Customers] AS [t0]
You can even build the property values in the database layer. Look at this query that constructs the Name from the first and last name stored in the database:
var q =
from e in db.Employees
select new {Name = e.FirstName + " " + e.LastName, Phone = e.HomePhone};
The generated SQL looks like this:
SELECT ([t0].[FirstName] + @p0) + [t0].[LastName] AS [value], [t0].[HomePhone]
FROM [Employees] AS [t0]
So, notice that the Name is returned as a single column. The database engine constructs the new field, not the client side code. Cool, huh?
Other projections can be performed at the database engine. This snippet of Linq code constructs a type that contains the product ID, and the price at a half-off sale:
var q =
from p in db.Products
select new {p.ProductID, HalfPrice = p.UnitPrice / 2};
Again, the generated SQL constructs the HalfPrice value:
SELECT [t0].[ProductID], [t0].[UnitPrice] / @p0 AS [value]
FROM [Products] AS [t0]
Later, of course, you get a request to create a simple report that tells you if a product is in stock:
var q =
from p in db.Products
select new {p.ProductName,
Availability = p.UnitsInStock - p.UnitsOnOrder < 0 ?
"Out Of Stock": "In Stock"};
Which generates this SQL query:
SELECT [t0].[ProductName],
(CASE
WHEN ([t0].[UnitsInStock] - [t0].[UnitsOnOrder]) < @p0 THEN @p1
ELSE @p2
END) AS [value]
FROM [Products] AS [t0]
I'm glad I didn't have to write that. The Linq2Sql libraries translated simple C# into whatever T-SQL you need.
We saw this type of query earlier, but you can just as easily project the results into your own predefined type (here, the Name type):
var q =
from e in db.Employees
select new Name {FirstName = e.FirstName, LastName = e.LastName};
Which generates this SQL query:
SELECT [t0].[FirstName], [t0].[LastName]
FROM [Employees] AS [t0]
But, remember that since the Name type is not part of the database model, instances of Name will not participate in the database updates.
You can generate queries that project one field from a table when a condition is met:
var q =
from c in db.Customers
where c.City == "London"
select c.ContactName;
Which translates to this SQL:
SELECT [t0].[ContactName]
FROM [Customers] AS [t0]
WHERE [t0].[City] = @p0
You can also project into more complicated structures:
var q =
from c in db.Customers
select new {
c.CustomerID,
CompanyInfo = new {c.CompanyName, c.City, c.Country},
ContactInfo = new {c.ContactName, c.ContactTitle}
};
The code above queries the customer table, and creates a new nested type. The main type contains a customer ID, and two nested types: a company info structure and a contact info structure. Look at the SQL generated:
SELECT [t0].[CustomerID], [t0].[CompanyName], [t0].[City], [t0].[Country],
[t0].[ContactName], [t0].[ContactTitle]
FROM [Customers] AS [t0]
Notice that it returns the fields in a flat record-like structure. The client code is responsible for constructing the proper types:
CustomerID=ALFKI CompanyInfo={ } ContactInfo={ }
CompanyInfo: CompanyName=Alfreds Futterkiste City=Berlin
Country=Germany ContactInfo: ContactName=Maria Anders ContactTitle=Sales Representative
CustomerID=ANATR CompanyInfo={ } ContactInfo={ }
CompanyInfo: CompanyName=Ana Trujillo Emparedados y helados
City=México D.F. Country=Mexico
ContactInfo: ContactName=Ana Trujillo ContactTitle=OwnerYou can also create nested queries:
var q =
from o in db.Orders
select new {
o.OrderID,
DiscountedProducts =
from od in o.OrderDetails
where od.Discount > 0.0
select od,
FreeShippingDiscount = o.Freight
};
This creates a nested structure, like the previous query. But, the DiscountedProducts property is a sequence of products where there is a discount.
The generated SQL Query looks like this. Notice that it is one round-trip to the database engine to get all the orders, and a second roundtrip to retrieve the order details for all the orders.
SELECT [t0].[OrderID], [t0].[Freight], (
SELECT COUNT(*)
FROM [Order Details] AS [t1]
WHERE ([t1].[Discount] > @p0) AND ([t1].[OrderID] = [t0].[OrderID])
) AS [DiscountedProducts]
FROM [Orders] AS [t0]
ORDER BY [t0].[OrderID]
SELECT [t1].[OrderID], [t1].[ProductID], [t1].[UnitPrice],
[t1].[Quantity], [t1].[Discount]
FROM [Orders] AS [t0], [Order Details] AS [t1]
WHERE ([t1].[Discount] > @p1) AND ([t1].[OrderID] = [t0].[OrderID])
ORDER BY [t0].[OrderID], [t1].[OrderID], [t1].[ProductID]
And the output looks like:
OrderID=10248 DiscountedProducts=... FreeShippingDiscount=32.3800
OrderID=10249 DiscountedProducts=... FreeShippingDiscount=11.6100
OrderID=10250 DiscountedProducts=... FreeShippingDiscount=65.8300
DiscountedProducts: OrderID=10250 ProductID=51 UnitPrice=42.4000
Quantity=35 Discount=0.15 Order={ } Product={ }
DiscountedProducts: OrderID=10250 ProductID=65 UnitPrice=16.8000
Quantity=15 Discount=0.15 Order={ } Product={ }
OrderID=10251 DiscountedProducts=... FreeShippingDiscount=41.3400
DiscountedProducts: OrderID=10251 ProductID=22 UnitPrice=16.8000
Quantity=6 Discount=0.05 Order={ } Product={ }
DiscountedProducts: OrderID=10251 ProductID=57 UnitPrice=15.6000
[[snip ]]
Distinct works as you would imagine:
var q = (
from c in db.Customers
select c.City )
.Distinct();
Generating the obvious SQL query:
SELECT DISTINCT [t0].[City]
FROM [Customers] AS [t0]
So, what have we covered? Well, Linq2SQL supports all the standard query operators (not that I covered all of them yet). And, it translates those query operators into the rather obvious SQL equivalent. That saves quite a bit of time, as the database engine can do what it does best, and you can express your queries in the language you are most comfortable using.
But, of course, there are some caveats. If you project the query results into a new type, you lose the ‘smarts’ that supports the round trip retrieve / modify / update cycle. So, be careful.
Next, I’ll cover the numeric operations in SQL: Min, Max, etc.
Part 1Introduction to LinqToSql
Part 2Simple queries
Deferred queries and hidden optimizationsNow, it’s time to look at the next set of Where samples in DLINQ.
Here’s the code for sample 2:
var q =
from e in db.Employees
where e.HireDate >= new DateTime(1994, 1, 1)
select e;
ObjectDumper.Write(q);
This time, I’m not shortening the output, because seeing all of it is instructive for one of the key DLINQ features. The query should give us all the information about every employee that was hired on or after January 1, 1994. Here is that output:
SELECT [t0].[EmployeeID], [t0].[LastName], [t0].[FirstName], [t0].[Title], [t0].[TitleOfCourtesy], [t0].[BirthDate], [t0].[HireDate], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[HomePhone], [t0].[Extension], [t0].[Photo], [t0].[ReportsTo], [t0].[PhotoPath]
FROM [Employees] AS [t0]
WHERE [t0].[HireDate] >= @p0
SELECT [t0].[Notes]
FROM [Employees] AS [t0]
WHERE [t0].[EmployeeID] = @p0
SELECT [t0].[EmployeeID], [t0].[LastName], [t0].[FirstName], [t0].[Title], [t0].[TitleOfCourtesy], [t0].[BirthDate], [t0].[HireDate], [t0].[Address], [t0].[City], [t0].[Region], [t0].[PostalCode], [t0].[Country], [t0].[HomePhone], [t0].[Extension], [t0].[Photo], [t0].[ReportsTo], [t0].[PhotoPath]
FROM [Employees] AS [t0]
WHERE [t0].[EmployeeID] = @p0
EmployeeID=7 LastName=King FirstName=Robert Title=Sales Representative TitleOfCourtesy=Mr. BirthDate=5/29/1960 HireDate=1/2/1994 Address=Edgeham Hollow
Winchester Way City=London Region=null PostalCode=RG1 9SP Country=UK HomePhone=(71) 555-5598 Extension=465 Photo=... Notes=Robert King served in the Peace Corps and traveled extensively before completing his degree in English at the University of Michigan in 1992, the year he joined the company. After completing a course entitled "Selling in Europe," he was transferred to the London office in March 1993. ReportsTo=5 PhotoPath=http://accweb/emmployees/davolio.bmp ReportsToEmployee={ } Employees=... EmployeeTerritories=... Orders=...
EmployeeID=8 LastName=Callahan FirstName=Laura Title=Inside Sales Coordinator TitleOfCourtesy=Ms. BirthDate=1/9/1958 HireDate=3/5/1994 Address=4726 - 11th Ave. N.E. City=Seattle Region=WA PostalCode=98105 Country=USA HomePhone=(206) 555-1189 Extension=2344 Photo=... Notes=SELECT [t0].[Notes]
FROM [Employees] AS [t0]
WHERE [t0].[EmployeeID] = @p0
Laura received a BA in psychology from the University of Washington. She has also completed a course in business French. She reads and writes French. ReportsTo=2 PhotoPath=http://accweb/emmployees/davolio.bmp ReportsToEmployee={ } Employees=... EmployeeTerritories=... Orders=...
EmployeeID=9 LastName=Dodsworth FirstName=Anne Title=Sales Representative TitleOfCourtesy=Ms. BirthDate=1/27/1966 HireDate=11/15/1994 Address=7 Houndstooth Rd. City=London Region=null PostalCode=WG2 7LT Country=UK HomePhone=(71) 555-4444 Extension=452 Photo=... Notes=SELECT [t0].[Notes]
FROM [Employees] AS [t0]
WHERE [t0].[EmployeeID] = @p0
Anne has a BA degree in English from St. Lawrence College. She is fluent in French and German. ReportsTo=5 PhotoPath=http://accweb/emmployees/davolio.bmp ReportsToEmployee={ } Employees=... EmployeeTerritories=... Orders=...
The most important item to see here is that there are sub-queries embedded amongst the output. The sub-queries retrieve the notes for that particular employee. What’s up with that? If you read my earlier posts on the core LINQ modules, you remember that the LINQ queries are deferred queries. They are executed only when you actually request the items in the sequence generated by the query. This concept is even more important and has greater ramifications in DLINQ.
When you look at the Employee class, you’ll find mostly fields and properties that are easily understandable. But, the notes field is a bit different:
private Link<string> _Notes;
The Link generic class implements a deferred query for that field. (I’m really simplifying here). The query to retrieve the Notes is executed only when the ObjectDumper requests the value of the Notes property for an employee. That’s why you see the queries after the other fields are displayed, and before the Notes are shown.
The key point is that DLinq provides a way to specify whether columns are retrieved immediately, or when the particular column is requested.
The next query shows that you can filter the returned rows from a database, just like for a set of objects:
var q =
from p in db.Products
where p.UnitsInStock <= p.ReorderLevel && !p.Discontinued
select p;
ObjectDumper.Write(q);
This method simply retrieves all products where the number in stock is less than the reorder level. A portion of the output is shown here. Note once again that the C# (or DLinq) query has been parsed and translated into SQL. Also note that the relationships are not followed, order details, category, and supplier are blank. I’ll explain how to retrieve related objects later.
SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock], [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued]
FROM [Products] AS [t0]
WHERE ([t0].[UnitsInStock] <= [t0].[ReorderLevel]) AND (NOT ([t0].[Discontinued] = 1))
ProductID=2 ProductName=Chang SupplierID=1 CategoryID=1 QuantityPerUnit=24 - 12 oz bottles UnitPrice=19.0000 UnitsInStock=17 UnitsOnOrder=40 ReorderLevel=25 Discontinued=False OrderDetails=... Category={ } Supplier={ }
ProductID=3 ProductName=Aniseed Syrup SupplierID=1 CategoryID=2 QuantityPerUnit=12 - 550 ml bottles UnitPrice=10.0000 UnitsInStock=13 UnitsOnOrder=70 ReorderLevel=25 Discontinued=False OrderDetails=... Category={ } Supplier={ }
The next Where sample shows another product query:
var q =
from p in db.Products
where p.UnitPrice > 10m || p.Discontinued
select p;
ObjectDumper.Write(q, 0);
The output shows a different query, and the products that match:
SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock], [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued]
FROM [Products] AS [t0]
WHERE ([t0].[UnitPrice] > @p0) OR ([t0].[Discontinued] = 1)
ProductID=1 ProductName=Chai SupplierID=1 CategoryID=1 QuantityPerUnit=10 boxes x 20 bags UnitPrice=18.0000 UnitsInStock=39 UnitsOnOrder=0 ReorderLevel=10 Discontinued=False OrderDetails=... Category={ } Supplier={ }
ProductID=2 ProductName=Chang SupplierID=1 CategoryID=1 QuantityPerUnit=24 - 12 oz bottles UnitPrice=19.0000 UnitsInStock=17 UnitsOnOrder=40 ReorderLevel=25 Discontinued=False OrderDetails=... Category={ } Supplier={ }
[[ snip ]]
The final sample that demonstrates where shows how to filter on two different Where clauses:
var q =
db.Products.Where(p=>p.UnitPrice > 10m).Where(p=>p.Discontinued);
ObjectDumper.Write(q, 0);
The abbreviated output is shown below. Notice that the DLinq library generates a single query. Only one trip to the database occurs, and the single set of answers is returned.
SELECT [t0].[ProductID], [t0].[ProductName], [t0].[SupplierID], [t0].[CategoryID], [t0].[QuantityPerUnit], [t0].[UnitPrice], [t0].[UnitsInStock], [t0].[UnitsOnOrder], [t0].[ReorderLevel], [t0].[Discontinued]
FROM [Products] AS [t0]
WHERE ([t0].[Discontinued] = 1) AND ([t0].[UnitPrice] > @p0)
ProductID=5 ProductName=Chef Anton's Gumbo Mix SupplierID=2 CategoryID=2 QuantityPerUnit=36 boxes UnitPrice=21.3500 UnitsInStock=0 UnitsOnOrder=0 ReorderLevel=0 Discontinued=True OrderDetails=... Category={ } Supplier={ }
ProductID=9 ProductName=Mishi Kobe Niku SupplierID=4 CategoryID=6 QuantityPerUnit=18 - 500 g pkgs. UnitPrice=97.0000 UnitsInStock=29 UnitsOnOrder=0 ReorderLevel=0 Discontinued=True OrderDetails=... Category={ } Supplier={ }
[[snip]]
Next, I’ll discuss more of the DLinq core libraries, hopefully with a shorter gap than the last blog entry.
DLINQ1The initiial discussion of DLinq
Where and how does that work?Now that we have finished the core LINQ queries, it's time to begin the LINQ to SQL (previously known as DLINQ) discussions.
The first LINQ to SQL sample is pretty simple:
public void DLinq1() {
var q =
from c in db.Customers
where c.City == "London"
select c;
ObjectDumper.Write(q);
}An abbreviated portion of the output looks like this:
SELECT [t0].[CustomerID], [t0].[CompanyName],
[t0].[ContactName], [t0].[ContactTitle], [t0].[Address],
[t0].[City], [t0].[Region], [t0].[PostalCode],
[t0].[Country], [t0].[Phone], [t0].[Fax]
FROM [Customers] AS [t0]
WHERE [t0].[City] = @p0
CustomerID=AROUT CompanyName=Around the Horn
ContactName=Thomas Hardy ContactTitle=Sales Representative
Address=120 Hanover Sq. City=London
Region=null PostalCode=WA1 1DP Country=UK
Phone=(171) 555-7788 Fax=(171) 555-6750
CustomerCustomerDemos=... Orders=...
etc.
Let's begin by looking at db. It's a member variable of the DLinq sample class:
private Northwind db;
Northwind is where (almost) all the new voodoo lives. So, I'll cover it in several blog posts. For this post, I'll only discuss the portions that you see in this sample.
Here's a part of the definition of the Northwind class:
public partial class Northwind : DataContext {
public Table<Customer> Customers;
// etc ...
}The DataContext class essentially provides the mapping between the relational database and the objects you want to work with. (I'm really oversimplifying, it's accurate as far as it goes.)
In the same sense, the Table<T> generic class provides the mapping from a table to a set (or sequence) of objects. And, yes, if you look deep enough into the Northwind.cs file, you'll find the Customer class defined. It's essentially a container that holds properties and fields for every column in the customer table.
The code looks very similar to everything you've seen before in the LINQ samples. But, there are a couple differences. If you look at the IL for DLinq1, you'll see that the compile time type of q is System.Query.IQueryable<nwind.Customer>. That makes sense, it's a queryable of customers. Now, when we look at the code in the debugger, the runtime type of q is System.Data.DLinq.DataQuery<nwind.Customer>. The DataQuery generic clss does the work of querying the database and returning each of the customers.
But finally, let's look at the output carefully. The DataContext class has a Log property that gets set to True in the Dlinq samples. That causes each of the samples to display the SQL used to do the work. In this case, the SQL is a select statement that returns each of the customers where the city matches the paramter.
Finally, you must remember that LINQ to SQL executes its queries in a lazy fashion. In this sample, it's a rather simple example, but it's an important concept. The DataQuery class contains an Expression Tree that defines all the lambda expressions you've used to create your query. Then, once you enumerate the results, the DataQuery object translates that expression tree into SQL statements. The SQL statements are sent to the database for execution (through the ADO.NET libraries you use today). And, you get the results.
In the next set of entries, we'll look at more of the Where samples to learn more about how the DataContext class and the DataQuery class translates your C# code into expression trees and eventually into SQL.
Because I want to get back to serious tech stuffJames Shore commented on my last post referring to Joel Spolsky and office space. He pointed me here: http://www.jamesshore.com/Agile-Book/sit_together.html where he discussed the advantages of having the entire team sit together in one large room.
I'll stand by my original post, where I said I need a place to think, without interruptions. I agree with his premise that members of the team need to communicate quickly, efficiently, and easily. However, I don't think forcing everyone to sit in the same room guarantees that, or does so without other costs. If the only way I can get team members talking is to put them all in one room, I've got a dysfunctional team, and I need to fix that. On the other hand, if I've got a team that performs well and communicates, the negative productivity impact of the interruptions, noise, and discomfort will outweight the benefits.
When I read James' example of workflow without everyone in the same room, I cringed. He says "Imagine that you're a programmer on a non-agile team and you need some information from your domain expert, Pat, in order to code an algorithm. You fire off an email, then take a break to stretch your legs and get some coffee."
He goes on to describe email tag lasting more than a day that ends up with the wrong answer. That's dumb. But, there are immediate solutions that don't mandate putting everyone in the same room:
. pick up the phone and call.
. use IM.
. Walk down the hall with a notebook, and have a chat.
In fact, one of the best office spaces I've used recently had a combination of private cubes, 4-person corrals, and small meeting rooms. Every developer has a laptop, and a 'home' in a private cube. When you need to work with someone, you pickup your laptop, and find a room, or use one of the corrals. It gives you the best of both worlds: privacy when you need or want it, and spaces for immediate face-to-face communications when you need that.
We now return to regularly scheduled C# discussions.