Friday, January 4, 2008

SqlBulkCopy - Copy Table Data Between SQL Servers at High Speeds - ADO.NET 2.0 New Feature

David Hayden ( Florida .NET Developer )

SqlBulkCopy is a new feature in ADO.NET 2.0 that gives you DTS-like speeds when you need to programmatically copy data from one database to another. Late last night I needed to harness SqlBulkCopy when for some unknown reason my beloved Red Gate Tools kept hanging when trying to transfer data from SQL Server 2000 to SQL Server 2005.

Lucky for me my situation was simple. I had identical tables on both databases. I just needed to populate those empty tables on SQL Server 2005 with the data on SQL Server 2000. It took me about 20 minutes to write the code as SqlBulkCopy does the bulk of the work.

Shown below is my slapped-together CopyData Class that accepts 2 connection strings, one for the source database and one for the destination database. A single method CopyTable is called with the name of the table whose data needs to be transferred from one database to another.

/// 
/// CopyData
///
public class CopyData
{
string _sourceConnectionString;
string _destinationConnectionString;

public CopyData(string sourceConnectionString,
string destinationConnectionString)
{
_sourceConnectionString
=
sourceConnectionString;
_destinationConnectionString
=
destinationConnectionString;
}

public void CopyTable(string table)
{
using (SqlConnection source =
new SqlConnection(_sourceConnectionString))
{
string sql = string.Format("SELECT * FROM [{0}]",
table);

SqlCommand command
= new SqlCommand(sql, source);

source.Open();
IDataReader dr
= command.ExecuteReader();

using (SqlBulkCopy copy =
new SqlBulkCopy(_destinationConnectionString))
{
copy.DestinationTableName
= table;
copy.WriteToServer(dr);
}
}
}
}

It assumes you passed in valid connection strings, the databases actually exist, and the table exists at both databases. If not, you can count on an unhandled SqlException being thrown at you.

SqlBulkCopy has the option of accepting an IDataReader as input, so the CopyTable method opens up a connection to the source database and does a “SELECT *” on the source table using the SqlCommand object's ExecuteReader.

An instance of SqlBulkCopy is created and passed the connectionstring of the destination database in its constructor. The name of the destination table is provided and WriteToServer takes all the information from the IDataReader and puts it in the empty destination table.

Copying table data is as simple as:

CopyData copier = new CopyData(".ConnectionString1.",
".ConnectionString2.");
copier.CopyTable(
".TableName.");

Conclusion

SqlBulkCopy is a very useful class in ADO.NET 2.0 for copying data between tables at high speeds.

(export data,import data)

Import / Export Excel Spreadsheet Data into SQL Server Database Table Using SqlBulkCopy

by David Hayden ( Florida ASP.NET C# Developer )

Using SqlBulkCopy to Import Excel Spreadsheet Data into SQL Server

Let's take an Excel Workbook with a worksheet, called Data, that contains 1000 rows of nonsense data broken into 2 columns, ID and Data.

I want to copy this data into a SQL Server Database Table, called ExcelData, with the same schema.

Just a little bit of code transfers the data from the Excel Spreadsheet into the SQL Server Database Table:

// Connection String to Excel Workbook
string excelConnectionString = @"Provider=Microsoft
.Jet.OLEDB.4.0;Data Source=Book1.xls;Extended
Properties=""Excel 8.0;HDR=YES;""
";

// Create Connection to Excel Workbook
using (OleDbConnection connection =
new OleDbConnection(excelConnectionString))
{
OleDbCommand command
= new OleDbCommand
(
"Select ID,Data FROM [Data$]", connection);

connection.Open();

// Create DbDataReader to Data Worksheet
using (DbDataReader dr = command.ExecuteReader())
{
// SQL Server Connection String
string sqlConnectionString = "Data Source=.;
Initial Catalog=Test;Integrated Security=True
";

// Bulk Copy to SQL Server
using (SqlBulkCopy bulkCopy =
new SqlBulkCopy(sqlConnectionString))
{
bulkCopy.DestinationTableName
= "ExcelData";
bulkCopy.WriteToServer(dr);
}
}

}

Conclusion

SqlBulkCopy will import / export your Excel Spreadsheet information into a SQL Server Database Table at very high speeds.(asp.net excel import or export, C# source code to import excel,import excel into SQL dataBase,import excel)

Wednesday, January 2, 2008

ASP .NET DataGrid DropDown Column - DotNet Zone - DNzone.COM

ASP .NET DataGrid DropDown Column - DotNet Zone - DNzone.COM

Use the ASP DataGrid DropDownColumn to create a single selection drop-down list control in each cell of DataGrid column. You can control the appearance of the ASP DataGrid DropDownColumn by setting the BackColor, ForeColor, BorderColor, BorderStyle, and other properties. The control displays in a datagrid cell a single value that is selected from a collection by clicking the button of the control. Your user can enter text in the control if the string typed in matched an item in the collection to be accepted.

The ASP DataGrid DropDownColumn supports data binding. To bind the control to a data source, create a data source, such as a System.Collections.ArrayList object, that contains the items to display in the column. Then, use the DataSource property to bind the data source to the ASP DataGrid DropDownColumn. You can use a System.Data.DataTable or System.Data.DataRowView or System.Collections.ArrayList object as a datasource for your DropDownColumn. For this kind of a datasource you need to specify ValueMember and DisplayMember properties.

By implementing in your code an interface with the DataGridCommands class you will be able to save all updates into DataGrid's datasource data object. By using UpdateDataSource method of the class you can easily save all updates that your user made in ASP DataGrid DropDown Column into bound datasource.

Syntax


The ASP DataGrid DropDown Column class has the following useful properties:

DataSource - a source for a DataGrid DropDown column values list as System.Data.DataTable or System.Data.DataRowView or System.Collections.ArrayList

DisplayMember - field to display in a column cell as String (name of a DataTable column)

ValueMember - field with values, which binds to drop-down control as String (name of table column). Specify the contents of the ValueMember property in cases where you bind data.

VB .NET
' Define DropDown variable as an object of DropDownColumn class
' and assign it to the DataGrid1 'State' column.
Dim DropDown As DropDownColumn = DataGrid1.Columns(5)
' Assign DataSource property for DropDown object as tblStates
' data table where states’s names are stored.
DropDown.DataSource = tblStates
' Specify DisplayMember as "Name" field of tblStates table
DropDown.DisplayMember = "Name"
' Specify ValueMember as "State" field of tblStates table
DropDown.ValueMember = "State"
' Identify "State" column's foreground color
DropDown.ForeColor = Color.DarkMagenta
' Identify "State" column characters' font and size
DropDown.Font_Name = "Tahoma"
DropDown.Font_Size = FontUnit.Point(8)
' Adjust 'State' column's width
DropDown.Width = Unit.Point(60)

C#
// Define DropDown variable as an object of DropDownColumn class
// and assign it to the DataGrid1 'State' column.
DropDownColumn DropDown = (DropDownColumn)DataGrid1.Columns[5];
// Assign DataSource property for DropDown object as tblStates
// data table where states’s names are stored.
DropDown.DataSource = tblStates;
// Specify DisplayMember as "Name" field of tblStates table
DropDown.DisplayMember = "Name";
// Specify ValueMember as "State" field of tblStates table
DropDown.ValueMember = "State";
// Identify "State" column's foreground color
DropDown.ForeColor = Color.DarkMagenta;
// Identify "State" column characters' font and size
DropDown.Font_Name = "Tahoma";
DropDown.Font_Size = FontUnit.Point(8);
// Adjust 'State' column's width
DropDown.Width = Unit.Point(60);

Maintaining checkbox "checked" state in ASP.NET DataGrid

Maintaining checkbox "checked" state in ASP.NET DataGrid

The subject of this blog should say it all. I've searched everywhere for a solution to this, to no avail. Essentially, what I wanted to do is this:
I needed to present a paged, filtered DataGrid to a user, with checkboxes added programmatically, and maintain the state of the checkboxes as PostBacks occurred.

There were tons of articles on how to programmatically add a checkbox to a DataGrid, which I had already figured out how to do. One way to do this (very inefficiently) is:

Stash the DataSet, DataView and DataGrid on the Session so it can be pulled after PostBacks. Then, iterate thru the DataGrid, then nest thru the DataSet, and set a Boolean value on the DataSet to match the value of the checkbox. So, you're looping thru the grid grid.Items.Count times, and for every item in the grid, you're looping thru the DataSet ds.Tables(”Table”).Rows.Count times. And that's just to capture the checkboxes.

As the user returns to that page of the grid, you'll need to pull the DataView off the Session, evaluate a column in the DataView against a column in the e.Items collection (from the DataGridItemEventArgs in the ItemCreated() event handler), and “check“ the box accordingly. Again, this is nested looping at it's worst.

This worked, but as I paged through, or as I changed the RowFilter on the dataview, the performance was horrible. I'm not know for writing efficient code, but this was ridiculous.

Make 'em sick, make 'em well.

So, after I filled my DataSet, I loaded my unique compare column (from the DataSet) along with a True/False value into a Collection. Since the Collection.Items() collection is read-only, I had to create a basic Class that exposed a property “CheckState“ to return True or False. That way, when I wanted to modify the value of the item in the collection for the given key, I could just modify a reference to the CheckState object. There will be code snippets below, btw.

So by now on PageLoad, I've got a filled DataSet, and a Collection representing key information. I stash that onto the Session, and I wait for a PostBack. When I get the PostBack, I loop thru the DataGrid, check the state of the checkboxes, and updating the collection accordingly. Again, stashing it back to the Session when I'm done.

When the page or RowFilter changes, I need to re-check checkboxes as necessary. I plugged into the ItemCreated event handler, and used the e.Items collection to fetch my key value, and I compared it to the Item in the Collection. If True, check the box.

Below is the code that I used to do all this (variable and table names have been changed to protect their anonymity).

Reply to this blog or email me if you wanna talk about it.

Private Sub Page_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
Dim lcv As Integer
Dim cb As CheckBox
Dim ds As DataSet
Dim oState As CheckState
If IsPostBack Then
' pull the collection off the Session
m_Collection = Session("Collection")
For lcv = 0 To dg.Items.Count - 1
' get a checkbox object from the datagrid to evaluate
cb = dg.Items(lcv).Cells(0).Controls(0)
' get the StateCheck object reference from the collection
' that matches the key column from the datagrid
oState = m_Collection.Item(dg.Items(lcv).Cells(1).Text)
' update the referenced object (which should update the collection)
If cb.Checked Then
oState.State = True
Else
oState.State = False
End If
' now that we've updated, push the collection back to Session
Session("Collection") = m_Collection
Next
Exit Sub
End If
PopulateGrid()
End Sub

Private Sub PopulateGrid()
Dim oDR As
SqlDataReader
Dim parmPostNum As
SqlParameter
Dim parm As
SqlParameter
Dim tableNames(0) As
String
Dim iCompanyID As
Int32
Dim dvPostMembers As
DataView
Dim lcv As
Integer
Dim lcv2 As
Integer
Dim cb As
CheckBox
Dim iCardNumber As
Integer
Dim oState As Integer
...
' Fill the DataSet here
...

m_Collection = New Collection

' set all to False, and add to the collection

For lcv = 0 To ds.Tables("Table").Rows.Count - 1
ds.Tables("Table").Rows(lcv).Item("CheckBoxChecked") =
False
oState = New
CheckState
oState.State =
False
m_Collection.Add(oState, ds.Tables("Table").Rows(lcv).Item("KeyValue"))
Next

Session("Collection") = m_Collection
dv = New DataView(ds.Tables("Table"))
dv.RowFilter = "Column='filter'"
Session("DataView") = dv
Session("DataSet") = ds
dg.DataSource = dv

dg.DataBind()

End Sub

Private Sub dg_ItemCreated(ByVal sender As Object, ByVal e As System.Web.UI.WebControls.DataGridItemEventArgs) Handles dg.ItemCreated
If (e.Item.ItemType = ListItemType.Item) Or (e.Item.ItemType = ListItemType.AlternatingItem)
Then
AddCheckbox(e)
End
If
End Sub

Private Sub AddCheckbox(ByVal e As DataGridItemEventArgs)
Dim cb As New
CheckBox
Dim dv As
DataView
Dim keyval As
String
Dim oState As
CheckState

dv = Session("DataView")
keyval = dv.Item(e.Item.ItemIndex).Row.Item(13)
' pull the collection off the Session
m_Collection = Session("Collection")

' get the CheckState of the current item in the data view
oState = m_Collection.Item(keyval)
cb.Checked = oState.State
cb.EnableViewState =
True
cb.ID = "chkItemChecked"
cb.AutoPostBack =
False
cell.HorizontalAlign = HorizontalAlign.Right
cell.Controls.Add(cb)
End Sub

Public Class CheckState
Private m_bCheckState As
Boolean
Public Property State() As
Boolean
Get
Return
m_bCheckState
End Get

Set(ByVal Value As Boolean)
m_bCheckState = Value
End Set

End Property
End
Class

Introduction - Custom ASP.NET Datagrid Paging With Exact Count - Developer Fusion - Visual Basic, C# Programming, ASP.NET, .NET Framework and Java Tut

Introduction - Custom ASP.NET Datagrid Paging With Exact Count - Developer Fusion - Visual Basic, C# Programming, ASP.NET, .NET Framework and Java Tutorials

Introduction

Anybody in the DB world knows what paging database results is and its effect. From the time I had started getting into good old classic ASP, I was intrigued with the ability to divide large sets of data into sections of x records per page. One thing that I didn't like about paging is that it seemed sites incorporated just a <> and Next > link on the search results page. I wasn't satisfied with such a lackluster paging technique, and from there I searched high and low on every ASP Web site I could find to see if there was code to show more advanced paging options, such as how many pages were remaining to be paged through, or, if the next page was the last page of results, how many records were on that last page. Unfortunately, I couldn't find any such code, so I had set out to do it myself. (To see the proposed paging enhancements I like, check out the live demo for this article...)

I have since coded a number of techniques for advanced paging in classic ASP, but my latest challenge has been to incorporate the same paging techniques in ASP.NET! (For more information on ASP.NET, be sure to visit the ASP.NET Article Index.) Pssst, don't ask me to talk about redoing my app in Beta 1, and upon upgrading to Beta 2 was horrified that my code needed to be redone.... by the way, the code in this article is all Beta 2 compliant.

Now, if anyone has looked into the Microsoft .NET SDK and Quickstart samples you will find custom paging samples, but it's the usual next and prev stuff. Now let's see how we can kick this paging up a notch and tell us more detail about our data output.

This article was originally published on 4guysfromrolla.com.

Dimitrios, or Jimmy as his friends call him, is a .NET developer/architect who specializes in Microsoft Technologies for creating high-performance and scalable data-driven enterprise Web and desktop applications. Till now Jimmy has authored nearly two dozen .NET articles, published on Dot Net Junkies, 4 Guys From Rolla, Sitepoint, MSDN Academic Alliance, Developers.NET, The Official Microsoft ASP.NET Site, and here on Developer Fusion, covering various unique and advanced techniques on .NET. more............

The ASP Column: DataList vs. DataGrid in ASP.NET -- MSDN Magazine, December 2001

The ASP Column: DataList vs. DataGrid in ASP.NET -- MSDN Magazine, December 2001

In my last column (September 2001) I looked at ASP standard server-side controls and showed how they simplify Web-based server-side UI by helping you manage the HTML that ends up on the client machine using programmatic abstractions (the server-side control classes for HTMLControls and WebControls). This month I want to look at two of the more specialized controls that come with the ASP.NET Framework: the DataList and the DataGrid. Both of these controls are full-featured data-managing tools. In addition to providing a simplified UI programming model for displaying data, ASP.NET includes facilities for binding data sources to these controls so you don't have to write all the code necessary to fish around a database and present your content. This month I'll compare and contrast the DataGrid and DataList controls, starting with DataGrid.
Grids (two-dimensional tables) are a classic way to present data. They've been around since the beginning of computing. The first killer application for the PC was the spreadsheet, a grid-oriented way of managing data. Throughout the 1990s, several companies made a lot of money selling grid controls—including Visual Basic® custom controls (VBX) in the early 1990s, and Java applets and ActiveX® controls during the mid- to late 1990s. It makes complete sense to have the equivalent functionality within ASP.NET.
For sample code this month, I've set up two separate pages with similar functionality—the ability to interactively select items from an inventory. However, one page uses a DataGrid and the other uses a DataList. I'll examine the features of each. This sort of functionality is very useful for Web site features such as shopping carts. Figure 1 shows a page that uses a DataGrid.

Figure 1 DataGrid Displaying Product Info
Figure 1 DataGrid Displaying Product Info

The idea behind the interface in this example is that you get to see all of the available inventory and select from the inventory by pressing a button. Before looking at how to accomplish this through the DataGrid, let's look at how ASP.NET manages binding data to these controls automatically.

Data Binding
DataGrid and DataList (among other controls) iterate over a collection automatically and render some HTML for each item (you specify the layout using templates). The automatic data binding of collections works for the following controls: HtmlSelect, CheckBoxList, DataGrid, DataList, Repeater, DropDownList, ComboBox, ListBox, and RadioButtonList. Each of these controls will automatically bind to the following classes representing collections: Array, ArrayList, HashTable, Queue, SortedList, Stack, StringCollection, DataView, DataTable, DataSet, SqlDataReader, and the OleDbDataReader.
Each of the controls I just listed (including DataGrid and DataList) include a property named DataSource. The DataSource property can be any System.Object, but for the data binding to work over a collection, it has to implement IEnumerable.
ASP.NET has a very convenient binding syntax. For example, Figure 2 includes a little bit of code that sets up an ArrayList full of data, a ListBox control, a set of RadioButtons, and a regular HTML selection control. Then the page binds the ArrayList to the various controls and renders the right HTML on the client.
Figure 2 includes some standard ASP.NET code describing a form with some standard server-side controls like you saw in the September ASP Column. The script block sets up an ArrayList and calls Control.DataBind (remember, System.Web.UI.Page derives from Control). DataBind connects the controls to their DataSource properties. Figure 3 shows the results of the data binding as they appear on the client's browser.

Figure 3 Data in the Browser
Figure 3 Data in the Browser

The DataGrid
Binding to a standard collection is straightforward enough. ASP.NET saves you the trouble of iterating through the list yourself and pushing the HTML out to the browser by hand. While these controls are definitely very useful, modern Web sites demand more sophisticated data presentation. To make it easier to create sophisticated UIs, ASP.NET includes the DataGrid control.
The ASP.NET DataGrid control renders a table as a multicolumn grid. The DataGrid can include different column types (heterogeneous columns) for defining the layout of the cell contents. These include bound, button, and template columns, among others. In addition, the DataGrid supports interactive functionality such as column sorting, editing, and commands.

Placing a DataGrid on an ASPX Page
When defining a DataGrid control on your ASP.NET page, you first generate a data source and plug it into the DataGrid control in a fashion similar to what I've described. The DataGrid control then displays the fields of a data source as columns in a table, and each record is represented by a separate row in the DataGrid. Figure 4 shows the ASPX source code for setting up the two DataGrid controls shown in Figure 1.
The page in Figure 1 includes two DataGrids. The first grid represents the available inventory. The second grid represents the selected items. The inventory grid is set up entirely using ASP.NET declarations. Notice that the DataGrid includes three bound columns, a button column, and a custom template column (which shows a picture). The column types are nested within the DataGrid declarations. The call to DataBinder.Eval within the TemplateColumn/ItemTemplate declarations instruct ASP.NET to pull out the image field and generate an image tag. Notice the syntax surrounding the call to DataBinder.Eval: the <%# and %> tags tell ASP.NET to bind the data coming from the call to Eval to the image control within the ItemTemplate.
The second grid doesn't declare any columns in the ASPX page. They're added programmatically in the code-behind page, as you'll see in a moment. Finally, notice that the SelectedItemsGrid points to SelectedItemsGrid_Command to handle commands (this is the function that will handle the button events). Figure 5 lists the different kinds of column types you may use with a DataGrid.

Programming the DataGrid
If all you want to do is show your data in rows and columns, you can install a table-oriented DataSource in your DataGrid and stop there. ASP.NET will automatically iterate through the table rows and show them. But it's often useful to add some interactive functionality to the DataGrid.
Figure 6 shows the skeleton code for managing the DataGrid. I'll go over each piece in detail.

Setting up a DataSource
The first step in programming a DataGrid is to set up its DataSource property. There are several ways to establish a DataSource. For most real-world applications, you'd probably use a SqlConnection object and a SqlCommand object to create a DataReader representing the database. I'll look at integrating ADO.NET with ASP.NET in a future column. For this example, I created a table by hand using the common language runtime (CLR) classes DataTable, DataRow, and DataView. The Page_Load and CreateDataSource method implementations can be found in the code download at the link at the top of this article.
Page_Load calls CreateDataSource and adds DataRow objects to the DataTable CLR class to form a table. Then CreateDataSource makes a DataView object out of the table and returns the DataView, where it is bound to the controls when the page loads. Remember the SelectedItemsGrid declared on the ASPX page without the column? SelectedItemsGrid's columns are added programmatically. In addition to creating and binding a data source for the InventoryGrid, Page_Load also creates a DataView for the SelectedItemsGrid. These steps are necessary to make the grid bind automatically. The loading code also adds a button column. (There's no reason the columns have to be added programmatically—I just did it this way to show it can be done.)

Selecting and Deselecting Items
The button column within the InventoryGrid is useless without a command handler. The method InventoryGrid_Command handles button presses within the InventoryGrid. Figure 7 shows the InventoryGrid_Command method.
Pressing the Add button on the client browser generates a command event for the InventoryGrid, which is picked up within InventoryGrid_Command. Because this is a DataGrid, the DataGridCommandEventArgs.Item property includes the row of data selected by the user represented by TableCells. If the command name (the ID of the control generating the command event) is Add, this method creates a new row and adds it to the SelectedItemsGrid.
Of course, users would probably also like to deselect items (as when removing items from a shopping cart). The SelectedItemsGrid includes a button column for removing items from the grid. Figure 8 shows the command handler for the SelectedItemsGrid.
The data selected for removal comes through the DataGridCommandEventArgs parameter in the form of a TableCell. Removing the item requires you to set up a filter to find the row that includes the product number. Once you find the row, you may delete it. The SelectedItemsView enables the data binding between the SelectedItemsData and the SelectedItemsGrid. Deleting the row from the view and then performing a DataBind on the SelectedItemsGrid removes the item from the DataGrid.

The DataList
As mentioned earlier, another alternative for displaying data is the DataList. The ASP.NET DataList displays items from a data source. However, rather than displaying rows and columns, the DataList displays the contents of your data in more of a "list of records" type of format using templates. The templates customize the appearance and content of the DataList. Figure 9 shows the same data from the DataGrid example. However, this example uses the DataList control to show the inventory. (This example uses the same DataGrid setup to display the selected items.)

Figure 9 Using the DataList Control
Figure 9 Using the DataList Control

Placing a DataList on the ASPX Page
The first step in setting up a DataList is to declare one on your ASPX page. This process is similar to declaring a DataGrid. Figure 10 shows the ASPX code for declaring the DataList shown in Figure 9.
As with the DataGrid declaration, the DataList sandwiches one or more template declarations between the DataList tags. The code in Figure 10 lists an ItemTemplate which will push the image tag out to the client, followed by the product name, product number, price, and a button to press to select the item. Also notice the example uses a SeparatorTemplate to define HTML tags to be pushed out to the client browser between items. Figure 11 lists the templates available for use with the DataList. Finally, the DataList in the example defines a SelectedItemTemplate which colors the background of the selected item light blue.

Programming the DataList
The DataList has a programmable aspect to it, just like the DataGrid does. Once the DataList is declared on the ASPX page, it needs to have a DataSource associated with it. This is done within the Page_Load handler, just as in the DataGrid example. Once the DataList control is bound to the DataSource, ASP.NET goes through each row within the DataSource and renders the tags defined within the templates. The main difference between the DataGrid and the DataList is that the DataGrid is modeled after a table while the model under the DataList is a list of rows.
The code for hooking up the DataList to its DataSource and for handling the button press to select an item (guitaromaniadatalist.cs) is available in the code download.

Selecting an Item within the DataList
DataList controls may handle commands (just as the DataGrid does). Guitaromaniadatalist.cs defines a method named InventoryList_Command, which responds to the Select button within the DataList. Whereas the DataGrid responds to commands by passing in the selected row data as a set of table cells, the DataList simply passes in the selected index. The handler selects a certain item within the InventoryList. When the page renders again, the selected item will be shown using the SelectedItem template (in this case, the selected item will have a light blue background). The handler also extracts the selected item's data from the DataSource (using the index of the selected item) and adds the item to the SelectedItemsGrid DataGrid.

Conclusion
As far as UI programming goes, the main contribution of ASP.NET lies in providing useful abstractions over the normal HTML-tag-style programming of classic ASP. Two of the main ASP.NET UI abstractions include the DataGrid and the DataList controls. Once connected to a data source, these two controls iterate through the rows in the data source, generating HTML at the browser end for rendering either a table (in the case of the DataGrid) or a simple list of rows (in the case of the DataList). The DataGrid displays a table in the standard row/column format while the DataList displays the data source as a list of records. In future columns I'll look more closely at binding data to controls—especially using ADO.NET.

Hierarchical Data and the ASP.NET DataGrid

Hierarchical Data and the ASP.NET DataGrid

As Web developers our motto has always been "why should the WinForms guys have all the cool stuff!" It has been both our frustration and delightful challenge to try and give our users as rich a UI experience as that of a thick client application. In the process we have had to come up with an amazing array of hacks and ingenious work-arounds to make it so. When ASP.NET previewed in beta we were like kids in a candy store rushing to try all the new sugar coated goodies that came with the .NET Framework. For the most part we breathed a collective sigh of contentment imagining all the great things we could do with these new tools and all the work-around code we would no longer need to write. As time wore on however, and Beta 1 led to Beta 2, the lustre came off some parts of our shiny new toy. For me one of those times was when I discovered that the ASP.NET DataGrid control didn't do hierarchical datasets. Rather than accepting the fact as a limitation of ASP.NET, I decided to resort to my old ways and test the limits of the DataGrid server control. What follows is one approach to displaying hierarchical data in an ASP.NET DataGrid.

The Concept
While there are a number of examples of showing hierarchical data using the Master/Detail record concept, what I was looking for was the ability to display collapsible child rows under those of their parents in the same grid. Something similar to the way the Winforms DataGrid works when you feed it a hierarchical dataset. Unfortunately the ASP.NET DataGrid will only accept one of the tables within a hierachical dataset in its .DataBind method. In order to work around this we need to create a single DataTable object that contains all the rows we want to show when all the nodes of the hierarchy are expanded. Along the way we will leave ourselves some clues in the DataTable rows that tell us which ones are parent nodes and which are children. Once we have created this specially formatted DataTable we can then bind it to the DataGrid, apply a few formatting functions, and presto; hierarchical data served up in an ASP.NET DataGrid control.

Getting The Data
The first thing we are going to need to get this job done is some hierarchical data. For the purposes of this sample I chose to use the Northwind DB that comes with SQL Server as it is something that most developers have access to (no pun intended). Rather than just a simple parent child relationship I thought we would really push the envelope and try three levels. The tables we are going to work with are the Customers, Orders, and Order Details tables. As usual when accessing data we start by creating a connection object. Seeing as we are connecting to a SQL Server database we can use the SQLClient Namespace and its objects to gain a little performance boost. I have declared a module level variable to contain my Connection object and then call the Open method in the Page's Load Handler.

Private Sub PageLoad(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
If Not Page.IsPostBack Then
If moConn.State <> ConnectionState.Open Then
moConn.ConnectionString = (AppSettings("connectionString"))
moConn.Open()
End If
LoadResults()
End If
End Sub

The only piece of this code worth remarking on is the line where we set moConn.ConnectionString. All we are doing here is storing the connection string in the Web.Config file and accessing it through the System.Configuration.ConfigurationSettings.AppSettings object. I find this a handy trick so that the assembly doesn't need to be recompiled when the project is redeployed on another machine and the connection string changes. Once we have an open connection we call the LoadResults Sub and the real work begins.

Private Sub LoadResults;
Dim oRow As DataRow
Dim oTable As DataTable = New DataTable("RESULTS")
Dim sSQL As String
Dim dOrderTotal As Decimal
Dim oDA As SqlClient.SqlDataAdapter
Dim oCustomerRow As DataRow
Dim oOrderRow As DataRow
Dim oDetailRow As DataRow
Dim oDS As New DataSet()

'set up columns in the results table
oTable.Columns.Add("CompanyName", GetType(System.String))
oTable.Columns.Add("OrderDate", GetType(System.DateTime))
oTable.Columns.Add("ProductName", GetType(System.String))
oTable.Columns.Add("Quantity", GetType(System.Int32))
oTable.Columns.Add("UnitPrice", GetType(System.Decimal))
oTable.Columns.Add("Total", GetType(System.Decimal))

'send a batch of selects as the data adapters SelectCommand
sSQL = "SELECT * FROM CUSTOMERS WHERE CompanyName < 'C' & _ " "SELECT * FROM ORDERS WHERE CustomerID < 'C'" & _ "SELECT [ORDER DETAILS].OrderId, [ORDER DETAILS].Quantity, [ORDER DETAILS].UnitPrice, " & _ "ProductName FROM [ORDER DETAILS], PRODUCTS WHERE [ORDER DETAILS].ProductId = PRODUCTS.ProductId" oDA = New SqlClient.SqlDataAdapter(sSQL, moConn) 'map the tables that are returned from the DB to the ones we will create in the dataset
oDA.TableMappings.Add("Customers", "Customers")
oDA.TableMappings.Add("Customers1", "Orders")
oDA.TableMappings.Add("Customers2", "Details")
oDA.Fill(oDS, "Customers")

'set up the relationships
oDS.Relations.Add("Customer_Order", oDS.Tables("Customers").Columns("CustomerID"), oDS.Tables("Orders").Columns("CustomerID"))
oDS.Relations.Add("Order_Detail", oDS.Tables("Orders").Columns("OrderId"), oDS.Tables("Details").Columns("OrderId"), False)

'loop through the tables getting child rows as necessary
For Each oCustomerRow In oDS.Tables("Customers").Rows
dOrderTotal = 0
oRow = oTable.NewRow()
oRow("CompanyName") = oCustomerRow("CompanyName")
oTable.Rows.Add(oRow)
For Each oOrderRow In oCustomerRow.GetChildRows("Customer_Order")
oRow = oTable.NewRow()
oRow("OrderDate") = oOrderRow("OrderDate") oTable.Rows.Add(oRow)
For Each oDetailRow In oOrderRow.GetChildRows("Order_Detail")
oRow = oTable.NewRow()
oRow("ProductName") = oDetailRow("ProductName")
oRow("Quantity") = oDetailRow("Quantity")
oRow("UnitPrice") = oDetailRow("UnitPrice")
'calculate on the fly
oRow("Total") = (oDetailRow("UnitPrice") * oDetailRow("Quantity"))
oTable.Rows.Add(oRow)
'add to running total
dOrderTotal += oRow("Total")
Next
Next
oRow = oTable.NewRow()
oRow("ProductName") = "Customer Total"
oRow("Total") = dOrderTotal
oTable.Rows.Add(oRow)
Next
dgValues.DataSource = oTable dgValues.DataBind()
SetHierarchical(False )
End Sub

Let's walk through this Sub. At the top we declare a number of variables including a DataTable to hold our specially formatted results as well as a DataSet and DataAdapter for pulling the hierarchical data out of the DB. We then set up the columns that we want to include in our "RESULTS" DataTable that we will eventually bind to the DataGrid. Once this is done we can begin the process of pulling the grandparent, parent, and child records from the database. This is done by assigning a batch of SQL select commands as the SelectCommand property of the DataAdapter. We send one SQL select for each table that we want created in the DataSet. In this case we will issue three, one for each level in our hierarchy. Note that the third SQL select is selecting from two different tables and includes a join between them. This is to include some data from a fourth table that is what we want to include as part of the Order Detail information. Because this data is in a table that has a one to one relationship with the Order Details table we can just lump it together with the other order details information in the DataSet. We then add three TableMappings to the DataAdapter to assign names to the DataTables that will be created in our DataSet. We will call these tables Customers, Orders and Details to represent the three levels in our hierarchy. Once we call the Fill method of the DataAdapter our three tables in the DataSet will be filled with the data that we selected with our three SQL statements. Another minor point is that I have added WHERE clauses in the SQL to limit the amount of data we return from the DB.

Now we have data in our DataSet but how does it know about which tables are parents and which are children? Just like we do in building a relational database we need to create relationships between the table objects. In ADO.NET we do this using the System.Data.DataRelation object. In the next two line of code we add two DataRelations to the DataSets relations collection. The first one is called Customer_Order and makes the CustomerID of the Customers table the parent column while the CustomerID associated with the Orders table is the child column. The second relationship is similar except that it joins the Orders and Details tables in the dataset using their respective OrderID columns. The one other difference is that I have set the CreateConstraints flag to false in the second instance. By default a DataRelation is created with constraints meaning that if a there are keys in the child table that don't exist in the parent table, an exception is thrown. In our case we didn't limit the selection of rows in the Details table but we did in the Orders table, so to avoid the exception we need to set the the CreateConstraints flag to False.

We are now ready to start reading the data out of the DataSet and into our RESULTS table. To do this we use a set of nested loops which iterate through the rows in each table in the DataSet. We create a new row in the RESULTS for each row in the Customers Table and then enter the Orders loop. The interesting thing about this loop is how we get the child rows that pertain to the current Customer row. The method of the DataRow that does this is called, oddly enough, GetChildRows. We pass it the name of the DataRelation that joins these two tables and the DataSet is smart enough to know which rows in the order table belong to the current customer. The same thing happens when we need to loop through the detail rows for each order but this time we pass the Order_Detail DataRelation to GetChildRows. At the end of the Customer loop we add another row that shows a total of dollars spent by that customer that we have been storing in the running total variable dCustomerTotal. You'll notice that for each row in all of the three tables in our DataSet we add another row to our RESULTS table. The difference from one row to the next however is which columns we populate. These are the clues that we will use when we apply our formatting functions to the DataGrid which will give it the functionality that we are looking for.

Format The DataGrid
At the end of the LoadResults Sub we call SetHierarchical, passing it a value of False. This is the Sub where we apply the formatting that will give our DataGrid the look an feel of that lusted after WinForms one. Let's look at the code.

Private Sub SetHierarchical(ByVal bExpanded As Boolean)
Dim iCount As Int32
For iCount = 0 To dgValues.Items.Count - 1
'set the bg colour of the Customer and Order rows and the plus minus cells
If dgValues.Items(iCount).Cells(1).Text <> " " Then
dgValues.Items(iCount).BackColor = System.Drawing.Color.Wheat
dgValues.Items(iCount).Cells(0).BackColor = System.Drawing.Color.Tan
End If
If dgValues.Items(iCount).Cells(3).Text <> " " Then
dgValues.Items(iCount).BackColor = System.Drawing.Color.AntiqueWhite
dgValues.Items(iCount).Cells(2).BackColor = System.Drawing.Color.Tan
End If
'set the bg colour of the total rows
If dgValues.Items(iCount).Cells(4).Text = "Customer Total" Then _
dgValues.Items(iCount).BackColor = System.Drawing.Color.AntiqueWhite
If bExpanded Then
'hide + on Test all rows where there is not an expandable node
If dgValues.Items(iCount).Cells(3).Text = " " Then
dgValues.Items(iCount).Cells(2).Controls(0).Visible = False
Else
'set the minus sign
CType(dgValues.Items(iCount).Cells(2).Controls(0), LinkButton).Text = "-"
CType(dgValues.Items(iCount).Cells(2).Controls(0), LinkButton).CssClass = "PlusMinus"
End If
'hide + on all Collection rows where there is not an expandable node
If dgValues.Items(iCount).Cells(1).Text = " " Then
dgValues.Items(iCount).Cells(0).Controls(0).Visible = False
Else
'set the minus sign
CType(dgValues.Items(iCount).Cells(0).Controls(0), LinkButton).Text = "-"
End If
Else
'hide + on all Order rows where there is not an expandable node
If dgValues.Items(iCount).Cells(3).Text = " " Then
dgValues.Items(iCount).Cells(2).Controls(0).Visible = False
Else
'set the plus sign
CType(dgValues.Items(iCount).Cells(2).Controls(0), LinkButton).Text = "+"
CType(dgValues.Items(iCount).Cells(2).Controls(0), LinkButton).CssClass = "PlusMinus"
End If
'hide + on all Customer rows where there is not an expandable node
If dgValues.Items(iCount).Cells(1).Text = " " Then
dgValues.Items(iCount).Cells(0).Controls(0).Visible = False
Else
'set the plus sign
CType(dgValues.Items(iCount).Cells(0).Controls(0), LinkButton).Text = "+"
CType(dgValues.Items(iCount).Cells(0).Controls(0), LinkButton).CssClass = "PlusMinus"
End If
'hide all child nodes and rows of the root nodes
If dgValues.Items(iCount).Cells(1).Text = " " Then dgValues.Items(iCount).Visible = < color =" #0000ff"> False
End If
Next
End Sub

Just because you have called the DataBind method of the DataGrid object doesn't mean that you have to live with the results format-wise. After calling DataBind you can access all the rows of data and change them one by one. It does add a little performance penalty of course but that's a small price to pay for some cool additional functionality. Another trick we have used in this DataGrid is that not all of the columns are databound. Two additional columns have been added to display the plus and minus signs that the user will click to expand and collapse the nodes on our grid. In the SetHierarchical sub we loop through all the rows in the DataGrid and format them based on the clues we added in the rows in RESULTS table. For example, we test to see that there is data in the second cell of each row. If there is then we know that it is a customer row (remember we left the CompanyName column blank in all others when filling the results table). Similarly if the fourth row is filled then it is an Order row. The bExpanded variable is boolean that tells us whether to initialize the grid with all nodes expanded or collapsed. We have passed in False so we will hit the Else part of this If statement. In this section of the code we hide the plus signs on all non expandable rows and most importantly we hide the rows themselves for all rows but those at the top or Customer level. Remember when we set the Visible property of a DataGridRow to False it will not be rendered. This way when the grid is displayed we are only rendering the rows that the user will see while the ViewState will continue to hold all the rows from our RESULTS table that we bound to for later use.

Expand And Collapse
At this point running the code should display the grid in the collapsed position. All that remains is to add the code behind the events that are triggered when the user clicks the plus or minus signs. On closer inspection of the two non bound rows that were added to the grid we discover that they are actually LinkButton columns with the Command name changed to a custom value. The DataGrid object was built with extensibility in mind so it allows you to change the Command name of any button column. Now when the user clicks the button instead of firing one of the prewired command events like the CancelCommand, it fires the more generic ItemCommand. This then is the event handler where we will put our expand and collapse code.

Private Sub dgValuesItemCommand(ByVal source As Object, _
ByVal e As System.Web.UI.WebControls.DataGridCommandEventArgs)
Handles
dgValues.ItemCommand
Dim iCount As Int32 = e.Item.ItemIndex + 1

If e.CommandName = "ExpandOrder" Then
If CType(e.Item.Cells(2).Controls(0), LinkButton).Text = "-" Then
'Hide all child rows in the node careful not to hide the Customer row
Do While dgValues.Items(iCount).Cells(3).Text = " " And dgValues.Items(iCount).Cells(1).Text = " "
dgValues.Items(iCount).Visible = False
iCount += 1
If iCount > = dgValues.Items.Count Then Exit Do
Loop

'change the minus to a plus
CType(e.Item.Cells(2).Controls(0), LinkButton).Text = "+"
Else
'Show all child rows in the node
Do While dgValues.Items(iCount).Cells(3).Text = " "
dgValues.Items(iCount).Visible = True
iCount += 1
If iCount > = dgValues.Items.Count Then Exit Do
Loop
'change the plus to a minus
CType(e.Item.Cells(2).Controls(0), LinkButton).Text = "-"
End If
ElseIf e.CommandName = "ExpandCustomer" Then
If CType(e.Item.Cells(0).Controls(0), LinkButton).Text = "-" Then
'Hide all child rows in the node
Do While dgValues.Items(iCount).Cells(1).Text = " "
dgValues.Items(iCount).Visible = False
'if this is an order row set the plus sign as we are collapsing it too
If dgValues.Items(iCount).Cells(3).Text <> " " Then
CType(dgValues.Items(iCount).Cells(2).Controls(0), LinkButton).Text = "+"
End If
iCount += 1
If iCount > = dgValues.Items.Count Then Exit Do
Loop
'hide the total row
dgValues.Items(iCount - 1).Visible = False
'change the minus to a plus
CType(e.Item.Cells(0).Controls(0), LinkButton).Text = "+"
Else
'Show all Order rows in the node
Do While dgValues.Items(iCount).Cells(1).Text = " "
If dgValues.Items(iCount).Cells(3).Text <> " " Then dgValues.Items(iCount).Visible = True
iCount += 1
If iCount > = dgValues.Items.Count Then Exit Do
Loop
'show the total row
dgValues.Items(iCount - 1).Visible = True
'change the plus to a minus
CType(e.Item.Cells(0).Controls(0), LinkButton).Text = "-"
End If
End If
End Sub

As you can see, we test e.CommandName for the string that we set in fig 2 above. For the Customer rows it is ExpandCustomer and for the Order rows it is ExpandOrder. Basically we do the same thing in both sections. If the node has a plus sign then we expand it otherwise, we collapse. Again we use the clues that we left in our data to decipher what type of row it is and take action based on that. There are a few tricky things that need to be done like checking for the last row in the grid and dealing with the total rows but most of this code is pretty simple plumbing.

Sky's The Limit...
Hopefully this gives you a good idea about how to interact with hierarchical data in the ASP.NET DataGrid. Don't stop here though. If you can use something like this then download the code and start customizing! If you think you might reuse an object like this then try creating a server control that inherits DataGrid and build the plumbing code right in. Adding the ability to edit in line would be another great feature. One of the great things about .NET is that there are many ways to approach any problem. Experiment, enjoy, and remember when working with .NET, the sky's the limit!

Monday, December 31, 2007

SQL Server function to Check Whether All Characters In a String Are in Uppercase or Not

SQL Server function to Check Whether All Characters In a String Are in Uppercase or Not

When I was developing a small application, I had to write a Microsoft SQL Server script to check whether all the characters in a given string are uppercase alphabets or not.

Here is the Microsoft SQL Server function code which performs the check. This code is compatible with Microsoft SQL Server 2000 and Microsoft SQL Server 2005.


/****************************************************************

* Purpose: Check whether all characters

* in a string are capital alphabets or or not

* Parameter: input string

* Output: 0 - on success; 1 on failure

****************************************************************/

CREATE FUNCTION fnChkAllCaps(@P_String VARCHAR(500))

RETURNS BIT

AS

BEGIN

DECLARE @V_RetValue BIT

DECLARE @V_Position INT

SET @V_Position = 1

SET @V_RetValue = 0

–Loop through all the characters

WHILE @V_Position <= DATALENGTH(@P_String)

AND @V_RetValue = 0

BEGIN

–Check if ascii value of the character is between 65 & 90

–Note: Ascii value of A is 65 and Z is 90

IF ASCII(SUBSTRING(@P_String, @V_Position, 1))

BETWEEN 65 AND 90

SELECT @V_RetValue = 0

ELSE

SELECT @V_RetValue = 1

–Move to next character

SET @V_Position = @V_Position + 1

END

–Return the value

RETURN @V_RetValue

END


Sample code to test the function

SELECT dbo.fnChkAllCaps(‘TECHTHOUGHTS’) – Returns 0

GO

SELECT dbo.fnChkAllCaps(‘TechThoughts’) – Returns 1

GO


The above function iterates through all the characters of a given input string and checks whether ASCII value of the characters is between 65 and 90 to verify it is an uppercase alphabet or not.

You might be wondering why is ASCII values check is between 65 and 90. It is because the ASCII value of uppercase A is 65 and uppercase Z is 90.

Soon I’ll rewrite the same code using SQL Server 2005 CLR functions and post it. I believe Microsoft SQL Server 2005 CLR functions perform this check very efficiently.