.NET Blog

Tony Cavaliere

 
My Favourite Albums
  And the Grappa wins.
E-mail me Send mail
Add to Technorati Favorites AddThis Feed Button

Subscribe to Cynot Why Not


Recent posts

Disclaimer

Hey unlike other bloggers I stand by what I say but just in case. The opinions expressed herein are my own except on Tuesday when the second card is not turned up otherwise it ain't worth squat.

© Copyright 2010

Postback, UpdatePanel or JSON which one?

Microsoft introduced AJAX as an add on to .NET 2.0. Today, AJAX is now included as part of the core release of .NET 3.5. With this technology it is significantly easier for developers to implement Web 2.0 type applications. Rather than posting the entire form and receiving the entire page, the browser can send a partial request and receive a partial response. This is known as partial rendering. In some cases, all that is required is adding a ScriptManager and an UpdatePanel control and you are done. If you wish to reduce the payload even further you can use JavaScript Object Notation (JSON) and reduce the request and response footprint even further. In this post, I will investigate each of these technics, that is, the postback, UpdatePanel and JSON and hopefully give some insight on when each is appropriate.

Prerequisites

  1. Visual Studio 2008. Visual Web Developer Express 2008 should be fine. This post will be using VS2008.
  2. SQL Server with the Northwind database. The express ver1sion should be fine.

Initial Setup

Start Visual Studio 2008 and create a new web site. Delete the default.aspx web form that was added. When done your solution should be similar to the following:

Figure 1: Initial Solution

F1 Initial Solution 

Next add a LINQ To SQL class to the web site. But before you do this make sure you have a data connection to the Northwind database. Right click on the web site and select Add New Item. Select LINQ To SQL Class and name it Northwind.dbml. The Add New Item dialog is shown in Figure 2.

Figure 2: Add LINQ To SQL Class

F2 Add LINQ To SQL Class

A dialog will appear asking whether you would like to place the LINQ To SQL class into the App_Code folder, answer yes. For this scenario, we will be constructing a query that returns all the products for a customer using postal code to identify the customer. This requires that we add the Customers, Orders, Order Details and Products table to the designer surface. In the Server Explorer, navigate to the Northwind connection (if it doesn't already exist then add the connection) and expand the tables node. Select each table and drag them to the designer surface. Once completed the designer window should appear similar to the following:

Figure 3: LINQ To SQL Object Model

F3 LINQ To SQL Object Model

Behind the scenes LINQ To SQL is generating object classes that mirrors the tables that were added to the designer surface. To view the source for these classes use the Class View window. There you should see the Customer, Order, Order_Detail and Product classes. You may need to save the dbml file before these classes become visible in the Class View. One last point the dbml file is an XML file that describes schema for tables. This XML file is used to generate the object classes.

The next step is to add the code that will be responsible for retrieving products by postal code. Here we will use a LINQ query and the query will return a ProductsByPostalCode object that is serializable. This serialization will be important when we implement code that uses JSON. Right click the APP_Code folder and select the Add New Item menu item. Next select the class icon and choose Products.vb as the name for the file. Finally, add the code in Listing 1 to the Products class.

Listing 1: Code to Retrieve Products by Postal Code.

Imports System

Imports System.ServiceModel

Imports System.Runtime.Serialization

 

Public Class Products

 

    Public Function GetByPostalCode(ByVal pc As String) As List(Of ProductsByPostalCode)

 

        Dim db As New NorthwindDataContext()

        Dim products = From c In db.Customers _

                       Join o In db.Orders On o.CustomerID Equals c.CustomerID _

                       Join d In db.Order_Details On d.OrderID Equals o.OrderID _

                       Join p In db.Products On d.ProductID Equals p.ProductID _

                       Where c.PostalCode.Equals(pc) _

                       Select New ProductsByPostalCode With { _

                            .CompanyName = c.CompanyName, _

                            .Quantity = d.Quantity, _

                            .ProductName = p.ProductName, _

                            .PostalCode = c.PostalCode, _

                            .Phone = c.Phone _

                        }

        Return products.ToList

 

    End Function

 

End Class

 

<DataContract()> _

Public Class ProductsByPostalCode

 

    Private _CompanyName As String

    <DataMember()> _

    Public Property CompanyName() As String

        Get

            Return _CompanyName

        End Get

        Set(ByVal value As String)

            _CompanyName = value

        End Set

    End Property

 

    Private _ProductName As String

    <DataMember()> _

    Public Property ProductName() As String

        Get

            Return _ProductName

        End Get

        Set(ByVal value As String)

            _ProductName = value

        End Set

    End Property

 

    Private _Quantity As Short

    <DataMember()> _

    Public Property Quantity() As Short

        Get

            Return _Quantity

        End Get

        Set(ByVal value As Short)

            _Quantity = value

        End Set

    End Property

 

    Private _PostalCode As String

    <DataMember()> _

    Public Property PostalCode() As String

        Get

            Return _PostalCode

        End Get

        Set(ByVal value As String)

            _PostalCode = value

        End Set

    End Property

 

    Private _Phone As String

    <DataMember()> _

    Public Property Phone() As String

        Get

            Return _Phone

        End Get

        Set(ByVal value As String)

            _Phone = value

        End Set

    End Property

 

End Class

The method GetProductsByPostalCode creates a LINQ query that joins the Customers, Orders, Order_Details and Products objects and selects the CompanyName, ProductName, Quantity, PostalCode and Phone for the specified postal code. This data is returned in a custom object, ProductsByPostalCode. Using an anonymous type would not have been approp1riate here as serializing anonymous types is not easily done (not even sure if it is at all possible). The PoductsByPostalCode class is attributed with <DataContact> and the only method GetByPostalCode is attributed by <DataMember>. These attributes specify that the type defines or implements a data contract and is serializable. The <Serializable> attribute could have been used but <Data Contract> is the preferred WCF way.

That's it, the data access layer is complete.

 

Postback

Implementing the postback model is fairly straight forward. Add a web form to the web site and call the web form, Postback.aspx. Next add a TextBox, Button and GridView. Afterwards the markup should look like Listing 2.

List 2: Postback.aspx markup code. 

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

    <title>Untitled Page</title>

</head>

<body>

    <form id="form1" runat="server">

    <div>

      <asp:TextBox ID="txtPostalCode" runat="server"></asp:TextBox>

      <asp:Button ID="btnGetProducts" runat="server" Text="Get Products"></asp:Button>

      <br/>

      <asp:GridView ID="gvProducts" runat="server"></asp:GridView>

    </div>

    </form>

</body>

</html>

Next in the code behind add the following code that gets executed when the button is clicked. This code calls the GetByPostalCode method that was created earlier.

List 3: Postback.aspx.vb code behind.

    Protected Sub btnGetProducts_Click(ByVal sender As Object, ByVal e As System.EventArgs) _

    Handles btnGetProducts.Click

        Dim prod As New Products

        gvProducts.DataSource = prod.GetByPostalCode(txtPostalCode.Text)

        gvProducts.DataBind()

    End Sub

Now lets take a look at the payload of this web application. To do this we will use a Fiddler a great tool for monitoring HTTP traffic. First start Fiddler and then start the Postback.aspx. Enter the postal code 12209 and click the Get Products button. You should see a table with 12 products, as shown in Figure 4.

Figure 4: Products returned for postal code 12209.

F4 Postabackaspx Products for 12209

In Fiddler, select the postback.aspx URL session and then select the Performance Statistics tab. The bytes sent are 1522 and bytes received are 5,295. If you would like to see the raw data sent to and from the browser, double click the session in Fiddler and select the Raw tab. It is apparent that the entire form is sent to the server and the server sends the entire page back to the browser.

Figure 5: Raw request and Response from Fiddler

F5 Fiddler Request and Response 

 

Partial Rendering with the UpdatePanel

This time we will use the AJAX UpdatePanel to fetch the products. Adding an UpdatePanel is extremely simple, all you need to do is add a ScriptManager tag and then surround the portion of the page you wish to render partially with an UpdatePanel control. Add a new web form to the site and give it a name UpdatePanel. Next copy the markup from Postback.aspx to the UpdatePanel.aspx and similarly copy the code behind from Postback.aspx.vb to UpdatePanel.aspx.vb.

Drag and drop the AJAX ScriptManager from the toolbox to the UpdatePanel.aspx making sure that the <ScriptManager> tag appears after the <form> tag. The next step is to add the AJAX UpdatePanel control to the UpdatePanel.aspx markup. Again this is done by dragging and dropping the AJAX UpdatePanel control from the toolbox. Add the <ContentTemplate> tags within the the <UpdatePanel> tags. Lastly, cut the markup for the Button, TextBox and GridView controls and paste them within the <ContentTemplate> tags. If all is well you should have code resembling Listing 4.

Listing 4: Markup with the UpdatePanel Control

<html xmlns="http://www.w3.org/1999/xhtml">

<head id="Head1" runat="server">

    <title>Untitled Page</title>

</head>

<body>

    <form id="form1" runat="server">

      <asp:ScriptManager ID="ScriptManager1" runat="server">

      </asp:ScriptManager>

    <div>

      <asp:UpdatePanel ID="UpdatePanel1" runat="server">

        <ContentTemplate>

          <asp:TextBox ID="txtPostalCode" runat="server"></asp:TextBox>

          <asp:Button ID="btnGetProducts" runat="server" Text="Get Products"></asp:Button>

          <br/>

          <asp:GridView ID="gvProducts" runat="server"></asp:GridView>       

        </ContentTemplate>

      </asp:UpdatePanel>

    </div>

    </form>

</body>

</html>

I have not included the button event handler code as it is identical to that of the Postback.aspx.vb.

Now let's look at the payload of this page. Start Fiddler and then navigate to the UpdatePanel.aspx page. You may need to set the UpdatePanel.aspx as the Start page. Next enter the postal code 12209 and select the Get Products button. The output from the page will be the same as in Figure 4. In Fiddler select the session that corresponds to the button click and then select the Performance Statistics tab. Fiddler reports that the bytes sent are 933 and bytes received are 5124. Not a huge saving, but let's not forget that the page consists almost entirely of the grid. What if the page contained other content that did not require refreshing then the payload difference between the postback model and the UpdatePanel would be significant. Figure 6 shows the request and response for the UpdatePanel. Note that the response only contains the markup that appears within the UpdatePanel. This is what is meant by partial rendering; only the markup that is requested is sent back in the HTTP response.  

Figure 6: UpdatePanel: Request and Response

F6 UpdatePanel Request and Response

This is great. By adding only a few lines of markup you can significantly reduce the payload of your page.

 

WCF and JSON

The last method I will demonstrate reduces the payload even more, but at a cost. JavaScript Object Notation (JSON) is an extremely terse way of serializing objects. Basically, JSON uses name-value pairs to serialize the object. XML serialization could be used, instead, but it would be more verbose and more importantly there is no built-in JavaScript support for XML serialization. Windows Communication Foundation (WCF) makes it extremely simple to create a service that uses JSON as it's transport mechanism.

To add an AJAX-enabled WCF Service, right click the web site and select Add New Item. The Add New Item dialog appears. Select the AJAX-enabled WCF Service template and name it ProductService.svc. Figure 7 shows the Add New Item dialog.

Figure 7: Add AJAX-enabled WCF Service.

F7 Add AJAX-enable WCF Service 

Adding the AJAX-enabled WCF Service causes three things to happen:

  1. ProductService.svc file is added to the web site. This is analogous to the asmx file generated file in web services.
  2. ProductService.vb file is added in the App_Code folder. This the code behind for the service. Remove the AspNetCompatibility attribute.
  3. The <system.serviceModel> node containing WCF configuration is added to the web.config file. Remove the line <serviceHostingEnvironment aspNetCompatibilityEnabled="true" /> from the web.config.

I've had issues with keeping the ASP.NET Compatibility setting. For some reason the client browser, at runtime, does not recognize the namespace (in this case CynotWhyNot). This is strange as intellisense recognizes the namespace.  

By default, the WCF service added will use JSON for serialization. Changing it to use XML requires a change to the <behaviors> section in the web.config. In order to use this service, changes are required to ProductService.vb, namely, we need to add the code to call the data access code. Listing 5 contains the necessary changes to call the Products GetByPostalCode method.

Listing 5: ProductService.vb

Imports System.ServiceModel

Imports System.ServiceModel.Activation

Imports System.ServiceModel.Web

 

<ServiceContract(Namespace:="CynotWhyNot.WhichOne")> _

Public Class ProductService

 

    <OperationContract()> _

    Public Function GetProducts(ByVal pc As String) As List(Of ProductsByPostalCode)

        Dim nwp As New Products

        Dim products As New List(Of ProductsByPostalCode)

        products = nwp.GetByPostalCode(pc)

        Return products

    End Function

 

End Class

Some points of interest:

  1. The <ServiceContract> attribute is added at the class level. All interfaces that are to be exposed as a service must have this attribute. Interestingly, when adding an AJAX-enabled WCF service no interface is generated. I have been looking for an explanation for why it behaves this way as I was under the impression that all WCF services must implement an interface that is attributed by the <ServiceContract> attribute. I have come across a few web casts that mention this as a convenience. Nonetheless it works fine.  It is good practise to add a namespace to avoid collisions. 
  2. The <OperationContract> attribute is added to the GetProducts method. The <OperationContract> attribute exposes the method as a service operation.

The WCF framework will automatically generate the JavaScript code required to invoke this service. To view the generated JavaScript, navigate the browser to the service and then  append /js to the URL. I am using FireFox here as it allows the view of the code directly in the browser. IE, on the other hand, r1equires that the JavaScript be saved to a file.

Figure 8: Generated JavaScript to Invoke the Service. 

F8 Javascript generated code

Note the function CynotWhyNot.WhichOne.ProductService.GetProducts. This will be the JavaScript function that will be called to asynchronously invoke the server side service.

With the WCF service in place we can now concentrate on the aspx page. This page will need to call the service, retrieve the product data from the call to the service and finally render the data.  Yes, dare I say it we need to write JavaScript. This is what I meant by there is a cost and that cost is coding HTML DOM using JavaScript.

Add a new web form to the web site and call it JSON.aspx. Next add the following code to JSON.aspx.

Listing 6: JSON.aspx Markup and Code

<html xmlns="http://www.w3.org/1999/xhtml">

<head runat="server">

    <title>Untitled Page</title>

</head>

 

<script type="text/javascript">

 

  function getProducts() {

    var postalCode = document.getElementById('txtPostalCode');

    CynotWhyNot.WhichOne.ProductService.GetProducts(postalCode.value,onGetProductsComplete);

  }

 

  function onGetProductsComplete(products) {

    clearRows();

    //If products are returned then go ahead and add rows to the table.

    if (products.length > 0)

    {

        for(var i = 0; i < products.length; i++)

        {         

            var product = products[i];

            addRow(

                product.CompanyName,

                product.Phone,

                product.PostalCode,

                product.ProductName,

                product.Quantity

            );

        }

    }

  }

 

  var c = "#EFF3FB";

  function addRow(company,phone,postal,product,quantity) {

    if (c == "#EFF3FB") { c = "white"; } else { c = "#EFF3FB"; }

    var table = document.getElementById('tblProducts');

    var row = table.insertRow(table.rows.length);

    row.bgColor = c;

    row.insertCell(0).appendChild(document.createTextNode(company));

    row.insertCell(1).appendChild(document.createTextNode(postal));

    row.insertCell(2).appendChild(document.createTextNode(phone));

    row.insertCell(3).appendChild(document.createTextNode(product));

    row.insertCell(4).appendChild(document.createTextNode(quantity));

  }

 

  function clearRows() {

    var table = document.getElementById('tblProducts');

    for(var i = table.rows.length - 1; i > 0 ; i--)

    {

      table.deleteRow(i);

    }

  }

 

</script>

 

<body>

    <form id="form1" runat="server">

    <div>

 

      <asp:ScriptManager ID="ScriptManager1" runat="server">

        <Services>

          <asp:ServiceReference Path="~/ProductService.svc" />

        </Services>

      </asp:ScriptManager>

 

      <asp:TextBox ID="txtPostalCode" runat="server"></asp:TextBox>

      <asp:Button ID="btnGetProducts" runat="server" Text="Get Products" OnClientClick="getProducts(); return false;"></asp:Button>

 

      <br/>

 

      <asp:Table ID="tblProducts" runat="server" EnableViewState="false" >

          <asp:TableHeaderRow BackColor="#DDDDDD" Font-Size="Small">

              <asp:TableHeaderCell HorizontalAlign="Left" Width="400px"><h2>Company</h2></asp:TableHeaderCell>

              <asp:TableHeaderCell HorizontalAlign="Left" Width="150px"><h2>Postal</h2></asp:TableHeaderCell>

              <asp:TableHeaderCell HorizontalAlign="Left" Width="150px"><h2>Phone</h2></asp:TableHeaderCell>

              <asp:TableHeaderCell HorizontalAlign="Left" Width="350px"><h2>Product</h2></asp:TableHeaderCell>

              <asp:TableHeaderCell HorizontalAlign="Left" Width="100px"><h2>Quantity</h2></asp:TableHeaderCell>

          </asp:TableHeaderRow>

      </asp:Table>

 

    </div>

    </form>

</body>

</html>

Let's breakdown this code.

The <ScriptManager> tag is added so that we can add a reference to ProductService.srv service. This will automatically cause the JavaScript generated code (see Figure 8) to be uploaded to the client browser. An added feature is that design time intellisense support for this JavaScript is included. I was impressed when I first saw this. The addition of the <ScriptManager> tag also causes the broser to upload the Microsoft Ajax library.

The button uses the OnClientClick attribute to call the custom function getProducts and then returns false. Returning false is needed otherwise the click action will cause a postback and we want to prevent this from happening.

The GridView has been replaced with a table and only the table header is added. The table rows containing the product information will be added using JavaScript.

Finally, we have four JavaScript functions contained within the script block. The function getProducts calls the JavaScript service proxy CynotWhyNot.WhichOne.ProductService.GetProducts passing two parameters; postalCode.value and onGetProductsComplete. postalCode.value is the postal code entered in the text box and  onGetProductsComplete is a function callback that is called when the service has completed. There are two optional parameters that have been omitted for brevity; a callback to a function in case an error occurred in the service and userContext which can be any data the developer wishes to pass to the callback function when the service successfully completed (in our case onGetProductsComplete).

The function onGetProductsComplete is called asynchronously when the service completes provided there were no errors. Any return value from the service call is returned as the first parameter to the callback. The great thing is this parameter is automatically de-serialized for you. That is, the parameter, products, in the callback to onGetProductsComplete  is a JavaScript object that parallels the server side object List(Of ProductsByPostalCode). Since JavaScript has no understanding of managed generics, the JavaScript object is returned as an array of objects of type ProductsByPostalCode.

The remaining JavaScript code is responsible for adding rows to the table. I  won't bore you with the details of it.

Run this page, enter a postal code of 12209 and then select the Get Products button. The resulting page is shown in figure 9.

Figure 9: Running JSON.aspx.

F9 Running JSON.aspx

The question is what kind of payload results when we use JSON. Start Fiddler and then browse to JSON.aspx, enter 12209 as the postal code and then select the Get Products button. Select the session in Fiddler and then click the Performance Statistics tab. The bytes sent is now only 546 and bytes received is 2408. Down by a factor of 2 from the previous ways. Now let's take a look at the actual data sent across the wire. Double click on the session in Fiddler and select the raw tab. The results are shown in Figure 10.

Figure 10: Data transmitted using JSON

F10 Data JSON

The request is reduced to {"pc":"12209"} and the response is reduced to a collection of name-value pairs. Now that's compact!

 

Summary

We started off by asking the question, Which One?

The traditional post back method is straight forward to code but has the largest payload as it always sends the entire form to the server and the server responds by sending the complete markup. The UpdatePanel is great way of reducing the payload. Implementation can be as simple as adding a few lines to the aspx file. Even though the payload is reduced, the server processing remains unaltered as the complete page life cycle process is run. Nonetheless, it is great way to improve performance. Lastly, JSON with WCF is by far the best way of minimizing payload. But this comes at a cost, the developer is required to write JavaScript code.

What I typically suggest:

  • Use postback if you are saving data, e.g., an entry form.
  • Use UpdatePanel when you need to update a portion of the screen that was initiated by the user.
  • Use JSON when you want to periodically update a portion of the screen. An example of this might be a stock price updated every minute.

 

Guess the movie

Out of order, I show you out of order. You don't know what out of order is, Mr. Trask. I'd show you, but I'm too old, I'm too tired, I'm too fuckin' blind. If I were the man I was five years ago, I'd take a FLAMETHROWER to this place! Out of order? Who the hell do you think you're talkin' to? I've been around, you know? There was a time I could see. And I have seen. Boys like these, younger than these, their arms torn out, their legs ripped off. But there isn't nothin' like the sight of an amputated spirit. There is no prosthetic for that. You think you're merely sending this splendid foot soldier back home to Oregon with his tail between his legs, but I say you are... executin' his soul! And why? Because he's not a Bairdman. Bairdmen. You hurt this boy, you're gonna be Baird bums, the lot of ya. And Harry, Jimmy, Trent, wherever you are out there, FUCK YOU TOO!

Currently rated 5.0 by 2 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: ASP.NET | JSON | WCF
Posted by CynotWhyNot on Wednesday, April 23, 2008 11:26 AM
Permalink | Comments (72) | Post RSSRSS comment feed

ReadOnly TextBox: What happened to the text?

While most of us are busy sleeping on Sunday mornings, I'm out having coffee with fellow developer's chatting about .NET stuff. The GTANetPro group get together once a week (Sunday @10AM). Each of us give an informal presentation on .NET technologies. It's a great way to tune up your presentation skills without the pressures of a formal atmosphere.

Last week Dave (SangYun Lee) gave a presentation on read only text boxes and how the value is not post back in ASP.NET 3.5. The same is true for ASP.NET 2.0. In 1.1 the value was post back correctly. Thanks to Dave for a great presentation.

Let me explain further. Figure 1 shows the behaviour of a read only text box with it's read only state is set declaratively with the ReadOnly attribute. When the user selects the button labelled Set Text with No Postback, the setText javascript function is called and sets the text to good day. Afterwards the users selects the Do Postback button and the text returns to it's previous state, that is, ReadOnly. The text is correctly posted back but the asp.net runtime is not setting the Text to good day. This was not the behaviour in ASP.NET 1.1. Is this a bug? I believe so! However, it looks like going forward this will be the behaviour.

Figure 1:

Step 1:
readonly1



Step 2:
readonly 2


Step 3:
readonly3
code1

 

So what do we do?

The next figure has similar code except this time the read only attribute is not set declaratively, but, rather, it is set in the code behind during the loading of the page. Again when the user selects the Set Text with No Postback button the text in the text box is set to good day. However, this time when the Do Postback button is selected the text after the post back is remains set to good day.

I consider this a work around and until Microsoft changes this behaviour you will need to set the read only attribute programmatically using the Attributes.Add method. Given that this behaviour exists in ASP.NET 2.0 and 3.5 it looks like we may be stuck with this for a while.

Figure 2:

Step 1: readonly4


Step 2:
readonly5


Step 3:
readonly6
code2 code3

 

Thanks again Dave for a great presentation!

Guess the movie

The path of the righteous man is beset on all sides by the iniquities of the selfish and the tyranny of evil men. Blessed is he, who in the name of charity and good will, shepherds the weak through the valley of darkness, for he is truly his brother's keeper and the finder of lost children. And I will strike down upon thee with great vengeance and furious anger those who would attempt to poison and destroy my brothers. And you will know my name is the Lord when I lay my vengeance upon thee.

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: ASP.NET
Posted by CynotWhyNot on Sunday, April 20, 2008 6:17 PM
Permalink | Comments (12) | Post RSSRSS comment feed

LINQ to SQL versus ADO, Let's get ready to rumble.

Language Integrated Query (LINQ) was introduced in VS 2008 and is a great way to query data. It is a standard query syntax that essentially provides a common way of querying data regardless of whether the data source in SQL, XML or in-memory objects.

Prior to .NET 3.5 developers would typically use ADO to query relational databases, specifically, if you were querying SQL Server you would be using the SQL ADO libraries. If you wanted to retrieve the data as quickly as possible then you used DataReaders, but it was the developer's responsibility to maintain the data. If performance was not an issue then developers would typically opt for Datasets, a more feature rich mechanism for retrieving and storing the data.

But how performant (I know it's not a word but ...) is LINQ to SQL? I decided to put it to the test.

I wrote a console application to test the performance of LINQ to SQL, ADO Datasets and ADO DataReaders. The application consists of 3 methods one to run LINQ to SQL, one for Datasets and the last for DataReaders. This code is shown in Listing 1. In order to level the playing field the DataReader method populates a custom object, MyCustomer. The code for this class is also included in the listing. In each case we are querying the customer table (91 rows), 10,000 times.

I am using the northwind database and SQLExpress.

Listing 1:

 

Imports System.Data

Imports System.Data.SqlClient

Imports System.Configuration

 

Module Module1

 

    Sub Main()

 

        Dim start As DateTime

        Dim [end] As DateTime

 

        start = DateTime.Now

        GetUsingLINQ()

        [end] = DateTime.Now

        ElapsedTime(start, [end], "Using LINQ")

 

        start = DateTime.Now

        GetUsingDataset()

        [end] = DateTime.Now

        ElapsedTime(start, [end], "Using ADO Dataset")

 

        start = DateTime.Now

        GetUsingDataReader()

        [end] = DateTime.Now

        ElapsedTime(start, [end], "Using ADO DataReader")

 

        System.Console.ReadLine()

 

    End Sub

 

    Private Sub ElapsedTime(ByVal s As DateTime, ByVal e As DateTime, ByVal msg As String)

        System.Console.WriteLine(msg + ": " + ((e.Ticks - s.Ticks) / 10000000.0).ToString + " secs")

    End Sub

 

    Private Sub GetUsingLINQ()

        Dim db As New NorthwindDataContext()

        Dim query = From c In db.Customers

        Dim i As Integer

        For i = 0 To 10000

            query.ToList()

        Next

    End Sub

 

    Private Sub GetUsingDataset()

 

        Dim cstrcol As ConnectionStringSettingsCollection = ConfigurationManager.ConnectionStrings

        Dim cs As String = cstrcol("LINQ2SQLvsADO.My.MySettings.NorthwindConnectionString").ToString

        Dim cn As New SqlConnection(cs)

 

        Dim query As String = _

            "select [CustomerID], [CompanyName], [ContactName], [ContactTitle], " + _

            "[Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax] " + _

            "from [dbo].[Customers]"

 

        Dim cmd As New SqlCommand(query, cn)

        Dim da As New SqlDataAdapter(cmd)

        Dim ds As New DataSet

 

        Dim i As Integer

        For i = 0 To 10000

            da.Fill(ds)

        Next

 

    End Sub

 

    Private Sub GetUsingDataReader()

 

        Dim cstrcol As ConnectionStringSettingsCollection = ConfigurationManager.ConnectionStrings

        Dim cs As String = cstrcol("LINQ2SQLvsADO.My.MySettings.NorthwindConnectionString").ToString

        Dim cn As New SqlConnection(cs)

 

        Dim query As String = _

            "select [CustomerID], [CompanyName], [ContactName], [ContactTitle], " + _

            "[Address], [City], [Region], [PostalCode], [Country], [Phone], [Fax] " + _

            "from [dbo].[Customers]"

 

        Dim cmd As New SqlCommand(query, cn)

        Dim dr As SqlDataReader

        cn.Open()

 

        Dim mc As New MyCustomer

 

        Dim i As Integer

        For i = 0 To 10000

            dr = cmd.ExecuteReader

            While dr.Read

                mc.CustomerID = dr("CustomerID").ToString

                mc.CompanyName = dr("CompanyName").ToString

                mc.ContactName = dr("ContactName").ToString

                mc.ContactTitle = dr("ContactTitle").ToString

                mc.Address = dr("Address").ToString

                mc.City = dr("City").ToString

                mc.PostalCode = dr("PostalCode").ToString

                mc.Country = dr("Country").ToString

                mc.Phone = dr("Phone").ToString

                mc.Fax = dr("Fax").ToString

                mc.Region = dr("Region").ToString

            End While

            dr.Close()

        Next

 

    End Sub

 

 

End Module

 

Public Class MyCustomer

 

    Private _CustomerID As String

    Public Property CustomerID() As String

        Get

            Return _CustomerID

        End Get

        Set(ByVal value As String)

            _CustomerID = value

        End Set

    End Property

    Private _CompanyName As String

    Public Property CompanyName() As String

        Get

            Return _CompanyName

        End Get

        Set(ByVal value As String)

            _CompanyName = value

        End Set

    End Property

    Private _ContactName As String

    Public Property ContactName() As String

        Get

            Return _ContactName

        End Get

        Set(ByVal value As String)

            _ContactName = value

        End Set

    End Property

    Private _ContactTitle As String

    Public Property ContactTitle() As String

        Get

            Return _ContactTitle

        End Get

        Set(ByVal value As String)

            _ContactTitle = value

        End Set

    End Property

    Private _Address As String

    Public Property Address() As String

        Get

            Return _Address

        End Get

        Set(ByVal value As String)

            _Address = value

        End Set

    End Property

    Private _City As String

    Public Property City() As String

        Get

            Return _City

        End Get

        Set(ByVal value As String)

            _City = value

        End Set

    End Property

    Private _Region As String

    Public Property Region() As String

        Get

            Return _Region

        End Get

        Set(ByVal value As String)

            _Region = value

        End Set

    End Property

    Private _PostalCode As String

    Public Property PostalCode() As String

        Get

            Return _PostalCode

        End Get

        Set(ByVal value As String)

            _PostalCode = value

        End Set

    End Property

    Private _Country As String

    Public Property Country() As String

        Get

            Return _Country

        End Get

        Set(ByVal value As String)

            _Country = value

        End Set

    End Property

    Private _Phone As String

    Public Property Phone() As String

        Get

            Return _Phone

        End Get

        Set(ByVal value As String)

            _Phone = value

        End Set

    End Property

    Private _Fax As String

    Public Property Fax() As String

        Get

            Return _Fax

        End Get

        Set(ByVal value As String)

            _Fax = value

        End Set

    End Property

 

End Class

Any guesses on which was faster, which was slowest? The results are in:

  •  
    • LINQ to SQL (12 seconds)
    • ADO Datasets (18 seconds)
    • ADO DataReader (8.9 seconds)

So LINQ is not quite as quick as DataReader but is faster than Datasets.

If you would like to try this out for yourself then cut paste the code above and add it to a module file. In addition you will need to add a LINQ to SQL Class and make sure you call it Northwind.dbml. The only table that is required is the Customers table

 

Guess the movie

Capricorn 15's. Born 2244. Enter the Carousel. This is the time of renewal.

Be the first to rate this post

  • Currently 0/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: LINQ
Posted by CynotWhyNot on Friday, April 18, 2008 2:55 PM
Permalink | Comments (19) | Post RSSRSS comment feed

Extension Method Libraries

Lately, I've been playing with extension methods. Extension methods are a way to add custom functionality to data types that are already defined without creating a new derived type. On the surface extension methods appear to be adding methods to existing classes, but under the hood the compiler is abstracted what developer's have been doing or years, that is, write static helper methods. Creating extension methods in VB.NET or C# is simple and there are great posts that detail how it's done; Scott Hanselman, Visual Basic Developer Center and ScottGu, too name a few.

What about extension method libraries? It has been well over 5 months since the official release of VS 2008, are there many libraries out there that make use of extension methods. The answer is yes and here is a list that I have come up with. As I find more I will update this list.

  • Dynamic LINQ: A set of extension methods that allow you to construct dynamic LINQ queries.
  • XML Serialization: A few extension methods to serialize/de-serialize objects using XML.
  • Lucas Extensions: Contains 40+ extension methods extending Bitmap, ByteArray, Enumerable, File, Math, Object, String classes, etc.
  • Extension Toolkit: Extends a variety of classes, such as, String, DateTime, Cache, etc.
  • DateTime Extensions: A set of fluent extension methods for System.DateTime.
  • Extension Method: A web site devoted to extension methods. As of April 15, 2008 they were 68 methods.

Guess the movie (From a movie I didn't particularly like, but westerns apparently do. Come on you know whom I am referring to.)

I'm pretty sure there's a lot more to life than being really, really, ridiculously good looking. And I plan on finding out what that is.

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: VB.NET
Posted by CynotWhyNot on Thursday, April 17, 2008 11:49 AM
Permalink | Comments (66) | Post RSSRSS comment feed

LINQ to XML, Object Graphs and the ListView control

I have a subscription to msdn magazine and am a regular reader. I especially try to read all the articles on any ASP.NET related topics. Dino Exposito is a regular contributor to this magazine and has written many great articles on ASP.NET. In the April 2008 he wrote an excellent article on the new ASP.NET ListView control, specially, he discusses in detail how to bind a ListView control to hierarchical data.

This is by no means the first article that I have read on the topic of ListView controls. In all the articles or blog postings that I have read, I have yet to see how to use LINQ to create custom objects with complex object graphs. Instead anonymous types are used. In this post I will demonstrate how to create a custom object graph from a LINQ to XML query. Furthermore, I will bind this object to a nested ListView control. I will use the same example as presented by Dino in the August edition of msdn magazine.

We will be creating a hierarchical menu that is rendered using LINQ to XML, ListView control and an XML file. Listing 1 shows the menu.xml file.

Listing 1: menu.xml

<?xml version="1.0" encoding="utf-8" ?>

<menu>

  <item>

    <title>Menu Title 1</title>

    <link url="url1-1" text="text1-1"></link>

    <link url="url1-2" text="text1-2"></link>

    <link url="url1-3" text="text1-3"></link>

    <link url="url1-4" text="text1-4"></link>

  </item>

  <item>

    <title>Menu Title 2</title>

    <link url="url2-1" text="text2-1"></link>

    <link url="url2-2" text="text2-2"></link>

    <link url="url2-3" text="text2-3"></link>

  </item>

  <item>

    <title>Menu Title 3</title>

    <link url="url3-1" text="text3-1"></link>

    <link url="url3-2" text="text3-2"></link>

  </item>

</menu>

The schema of this XML file is straight forward. Each <item> node contains a menu title and then has 1 or more links. Each link has the URL to navigate too and the title that is to be used as textual display for the link.

Ultimately we would like to bind this data to a nested ListView control. The markup for this nested ListView control is shown in Listing 2. The title is render in the outer ListView control, whereas, the links are render in the inner ListView control. The code bind will be responsible for binding the outer ListView. The inner is bound declaratively. Please note that DataSource for the inner ListView control must be named links.

List 2: Nested ListView Control

    <asp:ListView ID="ListViewMenu" runat="server" ItemPlaceholderID="PlaceHolder1">

 

      <LayoutTemplate>

          <asp:PlaceHolder ID="PlaceHolder1" runat="server"></asp:PlaceHolder>

      </LayoutTemplate>

      <ItemTemplate>

 

        <h1><%# Eval("title")%></h1>

 

        <asp:ListView ID="ListViewSubMenu" runat="server"

          DataSource='<%# Eval("links") %>' ItemPlaceholderID="PlaceHolder2">

 

          <LayoutTemplate>

            <ul><asp:PlaceHolder ID="PlaceHolder2" runat="server"></asp:PlaceHolder></ul>

          </LayoutTemplate>

 

          <ItemTemplate>

            <li><a href='<%# Eval("url") %>'><%#Eval("text")%></a></li>

          </ItemTemplate>

 

        </asp:ListView>

      </ItemTemplate>

    </asp:ListView>

Before we use custom objects, let's review how we can use LINQ to XML and anonymous types as a data source to a ListView control. Listing 3 shows the code behind that accomplishes this.

List 3: Binding ListView Control with an Anonymous Type

        Dim doc As XDocument = XDocument.Load(Server.MapPath("App_Data/Menu.xml"))

        Dim menu = From mi In doc.<menu>.<item> _

                   Select _

                        title = mi.<title>.Value, _

                        links = From link In mi.<link> _

                                Select _

                                    url = link.@url, _

                                    [text] = link.@text

 

        ListViewMenu.DataSource = menu

        ListViewMenu.DataBind()

In this code snippet we are using the LINQ to XML features of VB.NET to query the XML file. The doc.<menu>.<item> retrieves all the <item> children for the document. The content of the <item> node is loaded into an anonymous type having two properties; title and links. The property links is a collection that is populated by the sub query. In the end, the anonymous type, menu is of type IEnumerable(Of <anonymous type>). The menu object is then used as data source to the outer ListView control.

Anonymous type are great, however, there are cases when you may need to pass the data between methods, tiers or even across process boundaries. In this scenario you should us a custom type. To do this you must first create the classes. In our case two classes are required. Listing 4 shows the two classes.

List 4: Custom Types 

<DebuggerStepThrough()> _

Public Class Menu

    <DebuggerBrowsable(DebuggerBrowsableState.Never)> _

    Private _title As String

    Public Property title() As String

        Get

            Return _title

        End Get

        Set(ByVal value As String)

            _title = value

        End Set

    End Property

    <DebuggerBrowsable(DebuggerBrowsableState.Never)> _

    Private _links As IEnumerable(Of Link)

    Public Property links() As IEnumerable(Of Link)

        Get

            Return _links

        End Get

        Set(ByVal value As IEnumerable(Of Link))

            _links = value

        End Set

    End Property

End Class

 

<DebuggerStepThrough()> _

Public Class Link

    <DebuggerBrowsable(DebuggerBrowsableState.Never)> _

    Private _url As String

    Public Property url() As String

        Get

            Return _url

        End Get

        Set(ByVal value As String)

            _url = value

        End Set

    End Property

    <DebuggerBrowsable(DebuggerBrowsableState.Never)> _

    Private _text As String

    Public Property text() As String

        Get

            Return _text

        End Get

        Set(ByVal value As String)

            _text = value

        End Set

    End Property

End Class

The two classes are named, Menu and Link. Menu contains two properties; title and links. links is of type IEnumerable(Of Link) which is important as LINQ queries of this type return the generic type IEnumerable(Of T). I have tried other generic types that derive from IEnumerable(Of T) such as List(Of T) but they generate Unable to cast object of type... exception. Perhaps there is a way to explicitly cast the object but that would require further investigation. Perhaps a future blog post?

Listing 5 shows LINQ to XML code to make use of the custom type.

List 5: Binding ListView Control with a Custom Type

        Dim doc As XDocument = XDocument.Load(Server.MapPath("App_Data/Menu.xml"))

        Dim menu = From mi In doc.<menu>.<item> _

                   Select New Menu With { _

                        .title = mi.<title>.Value, _

                        .links = From l In mi.<link> _

                                Select New Link With { _

                                    .url = l.@url, _

                                    .[text] = l.@text _

                            } _

                    }

        ListViewMenu.DataSource = menu

        ListViewMenu.DataBind()

The menu object is now of type IEnumerable(Of Menu), which can be easily passed as a parameter to a method or serialized across the wire.

Guess the movie

You loved my father, I know. But so did I. That makes us brothers, doesn't it? Smile for me now, brother.

Currently rated 5.0 by 1 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Categories: ASP.NET | VB.NET | LINQ | XML
Posted by CynotWhyNot on Wednesday, April 16, 2008 4:10 PM
Permalink | Comments (71) | Post RSSRSS comment feed