September 2006 - Posts
At my current gig, we're getting ready to use Windows Communications Framework as the platform for our middle tier. Here's the simplified instructions on how to get rolling.
On a pristine machine with XP SP2 and .NET 2.0 installed, do the following.
1. Install Vista RC1, 3.0 Runtime and the Visual Studio extensions
2. Follow the instructions for the One Time WCF Samples Setup
3. If you installed IIS after installing .NET, register IIS by running aspnet_regiis -i in the .NET 2.0 directory
4. Explode the WCF samples from C:\Program Files\Microsoft SDKs\Windows\v6.0\Samples and run the Getting Started sample in WCFSamples\TechnologySamples\Basic\GettingStarted\CS\GettingStarted.sln
If the project runs then you're all ready to roll.
Once you get your model, database and mapping metadata files configured correctly for Entity Framework, one of the first exceptions that you're likely to face is the DataReader already open exception. It will normally surface when you're iterating over a result set and then lazy loading a child collection. In the following example I have a Contact entity that has a Phone collection. If I lazy load the Phones then I'll get the DataReader exception.
var contacts = from c in db.Contact
select c;
foreach (Contact c in contacts)
{
if (c.Phones.IsLoaded == false)
c.Phones.Load();
if (c.Phones.Count > 0)
{
Console.WriteLine(c.LastName);
foreach (ContactPhone p in c.Phones)
{
Console.WriteLine("\t"+p.ContactPhoneId);
}
}
}
The reason for the exception is that there is an open data reader while reading in the contacts and then the loading of the child Phone collection is attempting to open another DataReader. By default, the SqlClient ADO.NET driver does not allow this.
There are two ways to fix this. First, if you are using SQL Server 2005, you can just enable MARS in your connection string.
add name="YourDBConnectionString" connectionString="metadata=.;
provider=System.Data.SqlClient;provider connection string="Data Source=irv-dev-vms05;Initial Catalog=YourDB;Integrated Security=True;
MultipleActiveResultSets=true"" providerName="System.Data.Mapping" /
A second option is to read all the results into memory in one shot and close the connection. Then you are free to open another connection to the database to read in the child entities. So how do you control this? The trip to the database does not happen when the LINQ statement is assigned to the var in the code above. Instead, the query to the database happens during the inplicit call to the enumerator of the contacts collection. To force all the contacts to be loaded, simply copy them to a list in memory using the ToList method and then continue the normal processing.
var contacts = from c in db.Contact
select c;
List results = contacts.ToList();
foreach (Contact c in results)
{
if (c.Phones.IsLoaded == false)
c.Phones.Load();
if (c.Phones.Count > 0)
{
Console.WriteLine(c.LastName);
foreach (ContactPhone p in c.Phones)
{
Console.WriteLine("\t"+p.ContactPhoneId);
}
}
}
This works as expected. Another important realization here is that there is an open connection to the database as long as you are reading in records through that enumerator! A novice programmer may unwittingly include a massive number of time consuming operations within the foreach loop (calling a web service, for example, comes to mind) which will keep the connection to the database open for an inordinate length of time. Of course, you won't fall into that trap.
An important facet of programming with the Composite UI Application Block is understanding the sequence of events that fire during the lifetime of a WorkItem. For example, in the Activate event for your WorkItem you may want to enable some toolbar or menu items and then show a view.
WorkItem events fall into 3 categories. The startup event is RunStarted. The operating events are Activating, Activated, Deactivating and Deactivated. The shutdown events are Terminating and Terminated. These events follow the standard .NET conventions. For example, Activating fires before Activated and is cancelable.
RunStarted fires during the Run method. Activating fires before the WorkItem status changes to Active - Activated fires after. Deactivating fires before the WorkItem status changes to Inactive - Deactivated fires after the status change. Terminating fires before the WorkItem status changes to Terminated - Terminated fires after.
These WorkItem events are the foundation of WorkItemExtensions.
Debugging the Composite UI Application Block EventBroker can be a lesson in frustration. Often, an event subscription can do something substantial such as show a View or run a WorkItem. Should the event subscriber throw an exception, you get a not-so-helpful exception message caught at the publisher that reads something like "One or more exceptions occured while firing the topic 'YourEvent'". Gee, thanks. No stack. No inner exception. So how do you debug this sucker?
Of course, you could trace through your handler, line-by-line, but there is a better way. The trick is to catch first chance exceptions. Whenever you are running in debug mode in Visual Studio and an exception occurs you can control what happens. By default the exception will be thrown and handled by your application's code exactly as it would be at runtime in the production app. But, toggle a debugger setting and you can now tell Visual Studio to jump into the debugger at exactly where the exception occured with the stack at exactly the point BEORE the exception is thrown! This is a first chance exception because you have a chance to debug it before the exception happens. Its a great productivity tool, ESPECIALLY when you work on a team where some members love to eat exceptions!!! Now you can actually see the crap that your fellow programmers have swept under the rug and you can now properly debug your app.
Here's how to enable this magic.
1. In Visual Studio, press Control+Alt+E. You get the Exceptions window.
2. Notice that next to Common Language Runtime Exceptions, Thrown is not checked. Check it off at this level to debug into all CLS Exceptions or drill down if you are only interested in specific exception types.
3. Click Ok
Now you are in debugging bliss. Whenever an exception happens the Visual Studio debugger will be happily pointing you to exactly the source of the problem.
Back in the bad old days, before generics, writing custom event handlers to pass additional data to the event subscriber was a pain in the butt. With the built in .NET 1.0 EventHandler you can signal that an event occured. Thats' fine except that the event handler usually needs more information about an event in order to do its job. Under .NET 1.0 you have a couple of solutions. You can cast the sender object to a specific known type and pull off the properties you need or you could define a delegate for the EventHandler and also subclass the EventArgs class to define the additional data to be passed to the handler. Casting the sender leads to brittle, highly coupled code. Creating the delegate and EventArgs subclass is not overly difficult but it does eat some brain cycles and leads to an explosion of classes.
Fortunately, with CAB and .NET 2.0 generics there is a much easier way called DataEventArgs. No casting, no delegate and no EventArgs subclass needed. Here's how you use it. First, define your event along with the custom data that will be send to the handler like this:
public event EventHandler<DataEventArgs<Account>> AccountSelected;
In this example I am defining an event called AccountSelected and the event publisher will pass an Account object to the event subscriber. Account is just one of my domain model classes but it could be any type of object including something like a string or int.
Whenever a new Account is selected I can raise the AccountSelected event with this code:
public void OnAccountSelected(Account a)
{
if (AccountSelected != null)
{
AccountSelected(this, new DataEventArgs<Account>(a));
}
}
public void OnAccountSelected(Account a)
{
if (AccountSelected != null)
{
AccountSelected(this, new DataEventArgs<Account>(a));
}
} Now your subscriber (the event handler) simply reads the data off the passed DataEventArgs object.
public void AccountSelected(object sender, DataEventArgs<Account> e)
{
ShowView();
presenter.SelectAccount(e.Data);
}
public void AccountSelected(object sender, DataEventArgs<Account> e)
{
ShowView();
presenter.SelectAccount(e.Data);
} This is much simpler than the equivalent 1.0 code. The passed data is exposed in the Data property of the EventArgs object and it doesn't need to be casted to use it.
If you want to use this class just include it in your own library.
//===============================================================================
// Microsoft patterns & practices
// CompositeUI Application Block
//===============================================================================
// Copyright © Microsoft Corporation. All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.Practices.CompositeUI.Utility
{
public class DataEventArgs<TData> : EventArgs
{
TData data;
public DataEventArgs(TData data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
this.data = data;
}
public TData Data
{
get { return data; }
}
public override string ToString()
{
return data.ToString();
}
}
}
//===============================================================================
// Microsoft patterns & practices
// CompositeUI Application Block
//===============================================================================
// Copyright © Microsoft Corporation. All rights reserved.
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT
// LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
// FITNESS FOR A PARTICULAR PURPOSE.
//===============================================================================
using System;
using System.Collections.Generic;
using System.Text;
namespace Microsoft.Practices.CompositeUI.Utility
{
public class DataEventArgs<TData> : EventArgs
{
TData data;
public DataEventArgs(TData data)
{
if (data == null)
{
throw new ArgumentNullException("data");
}
this.data = data;
}
public TData Data
{
get { return data; }
}
public override string ToString()
{
return data.ToString();
}
}
}