Frank Wang's inspirations on .NET

IEnumerable<Inspiration> inspirations = from i in DataContext.Inspirations where i.Sharable == true select i

All we need is a better Windows Vista. Here comes Windows 7...

Three days into playing with the Windows 7 PDC bits, I've already fallen in love with the new OS. Microsoft has come a long way to get things right this time. I know a lot of people hate Windows Vista badly. I do too. But I always think that Windows Vista isn't that bad. What it really lacks is the common sense of software design: convenience for users . I noticed a lot of thoughts have been put into Windows 7, although in this early pre-beta stage it still looks pretty much the same as Vista. But it's those "common sense" thoughts that make the difference. It doesn't matter what you want to call it, a better Windows Vista or Windows 7. I'm happy to see the progress.  Here are some screen shots of Windows 7 to share with you.

A facelift to the Windows Update. But my concern is: Windows 7 is still pre-beta, and Microsoft released security patches for it already???

image

Display Settings is now a first-class citizen in the context menu. You don't have to go through Personalize to adjust the display settings.

image

A nice slider bar shows you what your video card can do.

image

Just a little more space between files/folders makes a whole lot difference. How many times did you open the "wrong" files? All we need is a better Windows Vista after all. Little things do add up together.

image

Gadgets no longer have to live inside the sidebar. You can place them anywhere you want on the desktop.

image

A less "informational" Control Panel. Folks at Redmond finally figured out that too much user friendliness is not user friendly at all.

image

Somewhere between classic view and categorical view. Simple is good. At least you know what you're looking at.

image

Photoshop? PaintShop Pro? Guess again. After 15 years, our old friend Paint has got a new face. I love those ribbons.

image

Calculator has been rebuilt from ground up. A new "Programmer Mode" has been introduced. Now you don't even have to write code to calculate a TimeSpan :-)

image

A new member to the Accessories family: Sticky notes!!!

image

A lightweight Word or a heavyweight WordPad. Whatever you call it, I am sure it is going to get more work done.

image

Keywords are highlighted in search results.

image

One-click file sharing.

image

A new "Content" view has been added to windows explorer. By the way, have you noticed the Burn button is missing from the explorer? It must have been the least used button ever.

image 

IE 8 is the default web browser for Windows 7.

image

Notification area is now fully customizable. I never think it's a good idea to have 20 icons in the tray. 

image



Enable Back button support in ASP.NET AJAX web sites

An inherent problem with AJAX-style applications is browser navigation. AJAX pages don’t post back when performing requests tasks so they can’t remember their “previous states”. The problem comes when users click the browser's back button. What happens is browsers don’t return to a previous “state” of the page, instead, the browser unloads the page entirely and returns to the previous page. Technically speaking, this is OK because one may argue it’s just how AJAX works. However, a more user-friendly design needs to be able tackle this problem.

The ASP.NET AJAX Framework came with native support for back button and history management back in July 2007, when it was still called ASP.NET Futures. You would have to use a History control back then to achieve this goal. In the latest ASP.NET 3.5 release, the History control was removed from the framework, and all its functionality was ported into ScriptManager. Adding back button support to your AJAX page has become easier than ever. In today’s post, we will be looking at a typical “back button”: navigate in AJAX page with tabbed content. And then we will solve the problem by introducing the back button support.

Let’s start with creating a new ASP.NET web site in Visual Studio 2008. I named the web site TabbedContent. Go ahead and add a ScriptManager and a UpdatePanel to the default.aspx form. This will enable AJAX in our site. Now drop a TabContainer control inside the UpdatePanel. Note the TabContainer control was shipped with AJAX Control Toolkit. If you don’t have the toolkit installed, you can download it from CodePlex.  Once you have placed the TabContainer in the UpdatePanel, you can add a few TabPanel controls to it, and of course you can throw any content onto each TabPanel at your choice. If you want, you can copy and paste what I have in the TabContainer. I created three tabs, each for an auto maker, listing the models in a BulletList control.

<cc1:TabContainer ID="TabContainer1" runat="server" ActiveTabIndex="0">
<cc1:TabPanel ID="tpAudi" runat="server" HeaderText="Audi">
    <ContentTemplate>
        <asp:BulletedList runat="server">
            <asp:ListItem>A4</asp:ListItem>
            <asp:ListItem>A5</asp:ListItem>
            <asp:ListItem>A6</asp:ListItem>
            <asp:ListItem>A8</asp:ListItem>
        </asp:BulletedList>
    </ContentTemplate>
</cc1:TabPanel>
<cc1:TabPanel ID="tpBMW" runat="server" HeaderText="BMW">
    <ContentTemplate>
        <asp:BulletedList ID="BulletedList1" runat="server">
            <asp:ListItem>335</asp:ListItem>
            <asp:ListItem>528</asp:ListItem>
            <asp:ListItem>745</asp:ListItem>
            <asp:ListItem>Z4</asp:ListItem>
        </asp:BulletedList>
    </ContentTemplate>
</cc1:TabPanel>
<cc1:TabPanel ID="tpMercedez" runat="server" HeaderText="Mercedez">
    <ContentTemplate>
        <asp:BulletedList ID="BulletedList2" runat="server">
            <asp:ListItem>CLK 500</asp:ListItem>
            <asp:ListItem>S 550</asp:ListItem>
            <asp:ListItem>E 350</asp:ListItem>
            <asp:ListItem>C 60 AMG</asp:ListItem>
        </asp:BulletedList>
    </ContentTemplate>
</cc1:TabPanel>
</cc1:TabContainer>

Now your web site should be complete.Go ahead and run it.

Audi BMW

As you can see, the AJAX page doesn't post back when I switch between the tabs. The back button is always disabled because everything stays on the same page. So you may be wondering what the problem is. Everything happens on one page with no page postbacks. Isn’t that great user experience? Well yes and no. Let’s introduce the problem by adding another page login.aspx. This page has nothing on it but a redirect link to default.aspx.

<form id="form1" runat="server">
    <div>
        <asp:HyperLink ID="hlDefault" runat="server" NavigateUrl="~/Default.aspx" Text="Go to default" />
    </div>
    </form>

 

 

 

Now set the login.aspx as the startup page and run the web site again. Click on “Go to default”. Note the browser’s back button becomes enabled when you get redirected to default.aspx from login.aspx.

image image

On the default.aspx page, switch to Mercedez from Audi, and then click the back button. Oops… what happened? Now you are on the login page again. Didn’t you want to go back to Audi from Mercedez? Imagine how annoying this can be for a car buyer who is comparing different brands. 

image image image

Now we are going to solve this problem by leveraging the new history feature in ScriptManager. The key thing here is have the browser remember the history points as we navigate in an AJAX page.

Step 1 – Enable history point in ScriptManager.

This is a new feature only available in ASP.NET 3.5 and later. It’s pretty simple. You just need to add EnableHistory=”true” to the ScriptManager on your page.

<asp:ScriptManager ID="ScriptManager1" runat="server" EnableHistory="true" 
/>

Step 2 – Store the current ActiveTabIndex property of the TabContainer control in ViewState. We will be using the stored value in a moment.

Place this code in your default.aspx code behind file.

protected int ActiveTabIndex
{
  get
  {
      return ViewState["ActiveTabIndex"] != null ? Convert.ToInt32(ViewState["ActiveTabIndex"]) : 0;
  }
  set
  {
      ViewState["ActiveTabIndex"] = value;
  }
}

Assign the initial value to ActiveTabIndex when default.aspx first loads.

protected void Page_Load(object sender, EventArgs e)
{
    if (!Page.IsPostBack)
    {
        ActiveTabIndex = 0;
    }
}

Step 3 – Wire up the TabContainer’s ActiveTabChanged event

protected void TabContainer1_ActiveTabChanged(object sender, EventArgs e)
{
   ActiveTabIndex = TabContainer1.ActiveTabIndex; // Update the ActiveTabIndex property
   ScriptManager1.AddHistoryPoint("ActiveTabIndex", ActiveTabIndex.ToString()); 
}

Every time the tab index is changed, we need to make sure the ScriptManager remembers the last “state” of the TabContainer. It’s important to set the AutoPostback property to true in order for the ActiveTabChanged event to be fired.

Step 4 – Wire up the ScriptManager’s Navigate event.

This event is raised during asynchronous postbacks automatically when the server-side history state changes.

<asp:ScriptManager ID="ScriptManager1" runat="server" EnableHistory="true" 
     onnavigate="ScriptManager1_Navigate" />

Our server-side code for navigation is very straightforward. All the history points were already stored during postbacks. Now we just need to retrieve the value and assign it to the TabContainer’s ActiveTabIndex property. The history points are represented by a NameValueCollection.

protected void ScriptManager1_Navigate(object sender, HistoryEventArgs e)
{
  TabContainer1.ActiveTabIndex = Convert.ToInt32(e.State["ActiveTabIndex"]);
}

With all the changes we have just made, we should be able to switch between the tabs by clicking on the back button. This is illustrated by the screen shots below.

image image  Goback imageimage

 

 

 

 

 

 

Finally, here’s the source code of the demonstration. Feel free to download it.



First look at Visual Studio 2010

The VS 2010 CTP is out!!! It's very timing - the CTP was released for public download just one day before PDC 2008. Visual Studio 2010 and the .NET Framework 4.0 mark the next generation of developer tools. If you want to know the key innovations in this new release, please visit the official overview from Microsoft.

This CTP is available only as a Virtual PC image, which can be downloaded here. Please note this VPC image requires Virtual PC 2007 SP1. If you haven't upgraded your VPC to SP1 yet, you can download it here. The image contains Windows 2008 Standard Edition and VS 2010 Team Foundation Suite CTP. 

I downloaded and installed the CTP on my development machine as soon as I saw the news this morning. Here are a few screen shots from the CTP.

Start page - Welcome

Welcome

Start page - Projects

Project

Start page - Visual Studio

VisualStudio

Enhanced recent projects menu

RecentProjects

Two Project templates have been added to the New Project dialog - WiX and Modeling

NewProject

Continuous support for multiple .NET frameworks. .NET 4.0 has been added.

 Frameworks

New database project templates support SQL 2000, 2005 and 2008

DBTemplates

SQL-CLR project templates have been moved under the new Microsoft SQL Server template group

SQL-CLR

Looks like there're a lot exciting things in VS 2010. I will definitely play with the CTP a little bit more and write more interesting posts here.



10 things a professional developer should never say

Over years of my development career, I have come across IT professionals who always over estimate themselves and under estimate the work that they’re doing. Such behavior creates giant issues for companies and clients, who always end up saying “the project will never be done” after months or even years of frustrations.  I picked 10 most irresponsible statements and listed them in this blog post. Don’t get me wrong. This is not against anyone of you. I made some of these statements myself years ago as well. But I have learned lessons the hard way from the ramifications. I’m sharing my experience with you in the hope that nobody will repeat the mistakes.

10. "I'm pretty sure we can do it. We just need some research"

No you can’t do it. Otherwise why do you need any research? Why would you talk about something that you don’t even know?

9. "The coding is done but I haven't tested it yet.”

Total deception for the product owners. You think they don’t know anything about technology?

8. "It's just a configuration change."

How about “It’s a configuration change” (without the “just”)? Did it ever occur to you that your code stopped working “ridiculously” after that configuration change?

7. "It's just a web service call."

How about “It’s a web service call” (without the “just”). Are you handling the NULL values returned by that web service by any chance?

6. "It should work. “

Where did you get the “should” from? Have you tested it?

5. “We have to do it this way because it’s cool new technology.”

No you don’t have to. Does you boss have enough budget for the cool technology? Can your client wait two months for you to get up to speed on the new cool stuff? The business we are in is a battlefield and not a playground.

4. “The system has to be designed in such a way that it can work with all the other systems.”

Ideally yes. But have you seen all the other systems yet? Why don’t we build something that works first? Again look at your boss’s budget and your own deadline.

3. “Let’s hard code this value for now. We will make it configurable later.”

Guess what? I never got a chance to change a value I hard coded two years ago.

2. “I’m looking at the bigger picture and you are not”.

Rule #1: Never underestimate your teammates. Your “bigger picture” can be dangerously perceptional.

1. “Don’t call me over engineering. I’m designing something that is going to work not only this time but also 5 years down the road.”

Counting the CPU clock before having a working system is pointless. Yes you ARE over engineering.



Serialization issue with timestamp in LINQ to SQL

Many of you use timestamp in LINQ to SQL because it's easier for us figure out if an object is new or not. This is specially useful when we need to track the state of objects in disconnected/N-tier scenarios.  An important thing to note, though, is that the timestamp column is mapped as a System.Data.Linq.Binary property in the LINQ generated object so it is part of the XML serialization when you use the LINQ to SQL objects in Web Services/WCF services. The real problem is that System.Data.Linq.Binary doesn't have a parameterless constructor. Take a look at the Binary's constructor documented in MSDN. There's no parameterless constructor available to the class. What this means is the serialization of timestamp will fail for sure when you pass the object around in your service calls.

C# 
public Binary(
    byte[] value
)
 

To prove my point, we can create a Web Service that returns a LINQ to SQL object with the timestamp field. First of all, let's add a timestamp column to the Products table in Northwind.

image

Save the table. Create a new Web Service project named NorthwindProductService. Add a LINQ to SQL file Northwind.dbml into the project. We only need the Products table in the dbml file for our sample to work.

DBML

Find the definition of the TimeStamp property in the LINQ generated class. You will see the type of this property is System.Data.Linq.Binary.

[Column(Storage="_TimeStamp", AutoSync=AutoSync.Always, DbType="rowversion NOT NULL", CanBeNull=false, IsDbGenerated=true, IsVersion=true, UpdateCheck=UpdateCheck.Never)]
    public System.Data.Linq.Binary TimeStamp
    {
        get
        {
            return this._TimeStamp;
        }
        set
        {
            if ((this._TimeStamp != value))
            {
                this.OnTimeStampChanging(value);
                this.SendPropertyChanging();
                this._TimeStamp = value;
                this.SendPropertyChanged("TimeStamp");
                this.OnTimeStampChanged();
            }
        }
    }
    

At last, let's add a simple web method called GetProductByID that returns a Product object from the LINQ to SQL data context.

[WebMethod]
public Product GetProductByID(int productID)
{
   return new NorthwindDataContext().Products.Where(p => p.ProductID == productID).SingleOrDefault();
}

Now run the Web Service from Visual Studio. The asmx file throws the serialization exception immediately. We haven't even got  a chance to invoke the GetProductByID method.  But the exception does make all the sense as an object without parameterless constructor cannot be serialized.

image

Don't be desperate yet. Every problem has a solution :-) LINQ to SQL has provided a way to change the data type of every property that is mapped to the actual table column. By default, LINQ uses the original type of the column. As you can see in the LINQ designer surface, the data type of TimeStamp  is automatically set to System.Data.Linq.Binary.

image

All you have to do is change the data type from System.Data.Linq.Binary to byte[], which doesn't need to be instantiated during the serialization.

image

Compile and run the Web Service again. It's no longer complaining about the serialization issue. The source code for the sample we did in this blog post can be downloaded from my Live SkyDrive.



My experience of VS 2008 SP1 installation: not so smooth

The release of VS 2008 SP1 last week was a bliss for .NET developers. Many professionals shared their installation experience on their blogs. Very few of them had a lot of troubles getting the long awaited service pack up and running, despite the lengthy process of the installation. My personal experience was not terribly painful as I anticipated, but it didn't go very well in the beginning either. This has nothing to do the setup package but has everything to do with the beta stuff I had on my box. Microsoft provided a Visual Studio 2008 Service Pack Preparation Tool to help us uninstall VS SP1 pre-release and some other VS service packs before installing SP1. However, it looks like the tool just wasn't for me :-)

VS 2008 SP1 Pre-Release and SQL Server 2008 RC0 were both installed on my computer. RC0, as well as all the other CTP versions of SQL 2008, add shell integration with Visual Studio when they install. The preparation tool attempts to uninstall the shell integration but it needs the ORIGINAL vs_shell.msi file. This was exactly how I got stuck and why I thought I was in the dead water. The installer remembers the physical location where the vs_shell.msi was orginally launched. This would be the extracted files folder (or DVD) of your SQL 2008 RC0/CTPs, NOT your Visual Studio 2008 installation CD. I accidentally deleted the RC0 installation files so I had to extract them again.

You should be in pretty good shape after you pass this step.  After the preparation tool finishes removal of the SP1 pre-release, don't just launch the SP1 installer yet. Microsoft has not officially documented that you need to reboot the system before installing SP1. But my attempt to install SP1 immediately after the removal of pre-releases completely failed with the typical infamous "installation failed due to fatal errors" message. So my suggestion is don't even think about it. Getting a unknown "fatal errors" message after a 30-minute wait for the progress bar to finish is absolutely frustrating. I rebooted the system and then kicked off the SP1 installer. No problems at all this time.



How to configure maxJsonLength in ASP.NET AJAX applications

Invoking web methods from client scripts is fun. But before you roll out your cool applications into the production, please don't forget to reconfigure the max JSON string length, represented by either the maxJsonLength value in the web configuration file or the MaxJsonLength property of the JavaScriptSerializer class.

The maximum length of JSON strings, by default is 2097152 characters, which is equivalent to 4 MB of Unicode string data. This should be good enough for most of the web applications. If the size of serialized data you want to send to the client is larger than this default value, you may change it either programmatically or declaratively (in the web.config file of the web site). However, I personally wouldn't go over the 4MB threshold because serializing a large chunk of data across the wire normally causes huge performance penalty. From my experience, you may want to take a serious look at your application design if you need JSON strings longer than 2097152 characters.

To set this value in your code, you can simply assign an integer to the JavaScriptSerializer.MaxJsonLength property. Alternatively, you can configure the max length in the system.web.extensions configuration section in web.config. The example below sets the maxJswonLength value to 500000 characters. Also bear in mind that the maxJsonLength in the web.config file automatically overrides the JavaScriptSerializer.MaxJsonLength property.

<system.web.extensions>
    <scripting>
      <webServices>
        <jsonSerialization maxJsonLength="500000">
        </jsonSerialization>
      </webServices>
    </scripting>
</system.web.extensions>


Shorten your conditional statements with the ?? operator

I enjoy the new C# language features very much. Recently I started using the new ?? operator in my current projects. The ?? operator defines the default value to be returned when a nullable type is assigned to a non-nullable type. It is specially handy when you need to return a default value from your custom methods. A good example would be you wanted to capture the query string values in a web page but you needed to avoid the null reference exception in case the query string doesn't exist in the request. Consider the following code.

   1: private string GetQueryStringValue(string name, string defaultValue)
   2: {
   3:    return Request[name] ?? defaultValue;
   4: }

If the query string doesn't exist, the string defaultValue will be returned.



Execute Transact-SQL statement asynchronously

Processing a large amount of data from SQL server in .NET applications can be challenging some times. A common concern that people have is it just takes too much time to get the data from SQL. Waiting for the entire result set to return and then handling it require a great amount of RAM usage. That's not the worst thing yet. Imagine how frustrated your users can get when they have to wait for minutes after they click a button. A better designed application should utilize the asynchronous pattern to the data retrieval and process the data as soon as it becomes available in the callback function.

.NET developers used to write their own asynchronous code to execute T-SQL statements back in the .NET 1.0 & 1.1 days. The System.Data.SqlClient namespace in .NET 2.0 shipped with a number of out-of-box asynchronous methods for T-SQL execution, including ExecuteReader, ExecuteNonQuery, ExecuteXmlReader etc. These methods strictly follow the standard MS naming conventions for asynchronous methods. Take ExecuteReader as an example, BeginExecuteReader and EndExecuteReader are provided. The only caveat, if any, is that the asynchronous processing of T-SQL requires SQL Server 2005 or higher versions. If you haven't paid attention yet, the Management Studio for SQL 2005 or 2008 execute "select" queries asynchronously, as you can see in the screen shot below. I am selecting 4.5 million records from a table. The query comes back in less than 2 seconds with the first set of records. At the same time, the Management Studio keeps rendering the rest of the records behind the scene.

image

To demonstrate how the asynchronous operation works with large result sets, we are going to create a similar UI to the query window you just saw above.

Let's start off with creating a new Windows Form project named AsyncReader. Go ahead and add the controls to the Windows Form as shown below.

image 

The end goal of the example is: when I click on the Execute button, the application connects to the Northwind database, executes the T-SQL statement entered in the text box, and displays the result set in the ListBox at the bottom asynchronously.

Double click on the Execute button to bring up the code editor. We are going to implement the OnClick event of this button.

private void button1_Click(object sender, EventArgs e)
{
    using (SqlConnection conn = new SqlConnection("Network Address=(local), Initial Catalog=Northwind; Integrated Security=true; Asynchronous Processing=true"))
    {
        using (SqlCommand cmd = new SqlCommand(tbQuery.Text, conn))
        {
            try
            {
                conn.Open();
                cmd.BeginExecuteReader(new AsyncCallback(QueryCallback), cmd);
            }
            catch (SqlException se)
            {
                MessageBox.Show(se.Message);
                conn.Close();
            }
        }
    }
}

The code above follows the standard procedure for reading data from a SQL database in a .NET application, except two things:

1.  There's an additional name-value pair in the connection string. "Asynchronous Processing=true" tells the SQL Server (2005 or above) that this application will be executing T-SQL statements asynchronously. This name-value pair MUST be included in the connection string to enable asynchronous operations. Otherwise, an InvalidOperationException will be thrown when the BeginExecuteReader is called.

2. We used BeginExecuteReader method instead of ExecuteReader. This is the key. We are not going to wait until the entire result set returns. A callback delegate "QueryCallback" was passed as the first parameter. This means we are going to process the result set in "QueryCallback". The second parameter is called "stateObject" and it is of the System.Object type. It really can be boxed from any type of object holding the user state that is passed into the callback procedure. In our example, we pass the SqlCommand as the state object because we will need it to get the actual reference of the SqlDataReader.

The execution may take 2 seconds, 2 minutes, or probably 2 hours, but we have just solved two problems here by doing things asynchronously. First of all, the UI will NOT freeze because the BeginExecuteReader fires the query off and returns to the UI thread IMMEDIATELY. The actual T-SQL execution happens in a separate work thread. We will demonstrate this in a moment. Secondly, the query may take hours to complete but we can start processing the result set as soon as the data comes back in our callback method. Let's take a look at what's going to happen in the callback method.

private void QueryCallback(IAsyncResult result)
{
    try
    {
        SqlCommand cmd = (SqlCommand)result.AsyncState;
        SqlDataReader reader = cmd.EndExecuteReader(result);
        while (reader.Read())
        {
             Invoke(new AddDataToResultDelegate(AddDataToResult), reader.GetString(0));
        }
        if (cmd.Connection.State.Equals(ConnectionState.Open))
        {
            cmd.Connection.Close();
        }
    }
    catch (Exception ex)
    {
        Invoke(new AddDataToResultDelegate(AddDataToResult), "Error: " + ex.Message);
    }
}

The first thing we do is un-box the AsynState back to the SqlCommand, which was supplied as the state object from BeginExecuteReader.  Then we finish the asynchronous reading by calling the EndExecuteReader, which returns the requested SqlDataReader. Remember that calling EndExecuteReader is NOT optional. When you call BeginExecuteReader to execute a T-SQL statement, you MUST call EndExecuteReader in order to complete the operation. If the process of executing the command has not yet finished, this method blocks until the operation is complete. Now that we've got hold of the reference to the SqlDataReader, we can do anything we want with the data it brings back.  In this case, we are going to display the data in the ListBox control named lbResult we placed on the form earlier.

private delegate void AddDataToResultDelegate(string text);
 
private void AddDataToResult(string Text)
{
    lbResult.Items.Add(Text);
}
 
private void DisplayInfo(SqlDataReader reader)
{
    Invoke(new AddDataToResultDelegate(AddDataToResult), reader.GetString(0));
}

We may not interact with the form and its contents from a different thread, and this callback procedure is all but guaranteed to be running from a different thread  than the form. Therefore you cannot simply call code that update the ListBox control, like this: AddDataToResult(reader.GetString(0)); Instead, we must call the procedure from the form's thread. One simple way to accomplish this is to call the Invoke method of the form, which calls the delegate AddDataToResultDelegatae supplied from the form's thread.

Up to this point, we have done all we need to do for this quick example. Compile it to make sure there're no errors, and then hit F5 to run the application. Enter "select * from customers" in the text box and click the Execute button.  So what did you get? Nothing!!! None of the records was added to the ListBox. We know there're 91 customers in the Northwind database. So what happened? Stop the application. Place a break point at the beginning of the QueryCallback method and run the application again. The program doesn't even stop at the breakpoint?!

Welcome to the asynchronous world, where nothing is what it seems. We know the problem already. The callback function expects the state object supplied by BeginExecuteReader, but it's not there any more. Take a closer look at the Execute button's OnClick event.

private void button1_Click(object sender, EventArgs e)
{
    using (SqlConnection conn = new SqlConnection("Network Address=(local), Initial Catalog=Northwind; Integrated Security=true; Asynchronous Processing=true"))
    {
        using (SqlCommand cmd = new SqlCommand(tbQuery.Text, conn))
        {
            try
            {
                conn.Open();
                cmd.BeginExecuteReader(new AsyncCallback(QueryCallback), cmd);
            }
            catch (SqlException se)
            {
                MessageBox.Show(se.Message);
                conn.Close();
            }
        }
    }
}

The two "using" statements are the first thing that came into our attention. We are supplying the SqlCommand instance as the object state to the callback function but it gets disposed by the "using" immediately after the asynchronous call is fired. If you scream "oh I should've thought of that", you are not alone. This is a common mistake that developers make from time to time in asynchronous programming. The state object are accessed by two threads (the form's thread and a work thread that executes the asynchronous T-SQL), so its reference needs to exist through the entire application context. This is why sometimes the state objects are also called "user context". The solution is obvious now - remove the "using" statements to make sure the SqlCommand lives the entire application's life cycle.

private void button1_Click(object sender, EventArgs e)
{
  SqlConnection conn = new SqlConnection("Network Address=localhost;Initial Catalog=Northwind;Integrated Security=true;Asynchronous Processing=true");
  SqlCommand cmd = new SqlCommand( tbQuery.Text, conn);
  try
  {
      conn.Open();
      cmd.BeginExecuteReader(new AsyncCallback(QueryCallback), cmd);
  }
  catch (SqlException se)
  {
      AddDataToResult(se.Message);
  }
  catch (Exception ex)
  {
      AddDataToResult(ex.Message);
  }
}

Run the program again. We got everything together this time. The form's UI is still responsive to the user interactions as the query executes in the background. The ListBox is filled up with all the 91 customer names after a slight delay.

image 

The demo code is all yours. You can download it here and play with it a little bit more. If you want to feel the true power of asynchronous processing, I suggest you connect this application to a database where you have millions of records.



Experience SQL Server 2008 RC0

Microsoft last week rolled out the first Release Candidate of SQL Server 2008. The RC0 bits are available for public download here. I have been playing with SQL 2008 RC0 for a few days already, and I would like to share my experience in this quick blog post.

First of all, I have to warn you that my RC0 installation experience was not pleasant at all. If you are an early adopter of new releases like me, you will very likely run into the same troubles I had.

Previous CTP releases of SQL Server 2008 "Katmai" must be uninstalled first. The annoying part was the installation requirement check process doesn't warn you about the previous installations. Instead, it reports a fatal error as soon as the installation has started, because the system database files are being overwritten.

SQL Server 2005 Express can NOT exist on the machine where you are installing RC0. The installation wizard will tell you the check on "SQL Server 2005 Express Tools" fails and it must be uninstalled. I had a hard time looking for "Express tools" so I ended up uninstalling the entire SQL Server 2005 Express. Remember to reboot your machine before you attempt to install RC0 again :-)

I am not a SQL guy so I have played with the management studio for the most part. First thing I noticed was the splash screen is prettier than the SQL 2005 :-)

image

IntelliSense, introduced in the Feb 2008 CTP of SQL Server 2008, is still available in RC0. Please refer to my previous blog post if you want to know more about the IntelliSense in SQL 2008.

image

Real-time syntax check is a feature I missed in my previous blog. But yeah it's also very cool.

image 

An out-of-the-box Activity Monitor is provided to DBAs for real-time monitoring the health of the database server.

image

The most stunning new feature in RC0, I have to say, is the query debugging in the management studio. If you have paid attention to the previous screen shots, you should've noticed the new "Debug" button in the toolbar already.

image

I have two queries ready to go in the window above. Now let's hit the green debug button. What are you expecting?

image

Very nice! Our favorite yellow debug arrow is showing up in the SQL management studio now. Press F10 to step over the first query. We have got the first record set already in the result panel, and the second query is waiting to be executed.

image

The debugging can be really helpful when you need to investigate some logics in the database. Here's a better example.

image

How many times have you copied a result set into Excel and manually added the headers to the sheet? And for how long have you been doing this? SQL 2000, SP1, 2,3,4, SQL 2005, SP1, SP2, SQL 2008 "Katmai', July 2007 CTP, Feb 2008 CTP...

Well, the folks at Redmond could've made our life easy a long time back. I don't know why we've waited so long for such a small feature. "Copy with Headers" is finally here, in RC0 of SQL 2008.

image

I have the feeling that I probably will have to publish another blog post about the new features in SQL 2008 RC0. There're just too many things to cover. I am certainly looking forward to your comments as well, if you happen to discover other cool stuff before I do.