Monday, August 31, 2009

A SearchDialog Using SqlCeResultSet

On my previous post My First Journey to Mobile Database Application, I have mentioned using SearchDialog instead of ComboBox. On this post I will explain about the SearchDialog used by us. OK enough talking, let's get to the code.
I have documented almost every lines of our codes (FrmSearchDialog.vb). I hope you get the idea behind the SearchDialog. Here they are:
Imports System.Data
Imports System.Data.SqlServerCe
Public Class FrmSearchDialog
''' <summary>
''' Connection with connection string retrieved from a dictionary.
''' The dictionary values are loaded from a RSA encrypted text file.
''' The connection must be always opened for browsing SqlCeResultSet.
''' </summary>
Protected CconCurrent As New SqlCeConnection(DictConnectionString("SqlCeConnectionString"))
''' <summary>
''' Command used for searching.
''' </summary>
Protected CcmdSearch As New SqlCeCommand("", CconCurrent)
''' <summary>
''' ResultSet for storing select result.
''' </summary>
Protected CrsSearch As SqlCeResultSet
''' <summary>
''' HashTable for storing search result row.
''' </summary>
Protected HashSearchResult As Hashtable = Nothing
''' <summary>
''' String for storing SQL passed by user.
''' </summary>
Protected strSelect As String = ""
''' <summary>
''' String for storing Criteria passed by user.
''' </summary>
Protected strCriteria As String = ""
''' <summary>
''' String for storing strSelect + strCriteriaClause + strCritera + incremental search criteria.
''' </summary>
Protected strSearchSelect As String = ""
''' <summary>
''' List for storing ColumnTypes string for each column.
''' </summary>
Protected strColumnTypes As New List(Of String)
''' <summary>
''' Boolean for storing whether strSelect is a group by SQL.
''' </summary>
Protected boolGroupBy As Boolean = False
''' <summary>
''' String for storing criteria clause where for ordinary select and having for boolGroupBy=true.
''' </summary>
Protected strCriteriaClause As String = " where "
''' <summary>
''' A static function for opening a SearchDialog for the select statement and Criteria passed by user.
''' </summary>
''' <param name="SelectSql">The select statement.</param>
''' <param name="HashSearchResult">The search result row HashTable reference.</param>
''' <param name="DefaultCriteria">The select criteria.</param>
''' <param name="AllowSearch">if set to <c>true</c> allow search.</param>
''' <returns>True if OK is clicked and false if Cancel is clicked.</returns>
Shared Function Execute(ByVal SelectSql As String, ByRef HashSearchResult As Hashtable, Optional ByVal DefaultCriteria As String = "", Optional ByVal AllowSearch As Boolean = True) As Boolean
'Create an instance of FrmSearchDialog because this function is static.
Dim aFrmSearchDialog As New FrmSearchDialog
With aFrmSearchDialog
'Initializing allow search state
.MnuSearch.Enabled = AllowSearch
.CmbColumn.Enabled = AllowSearch
.TxtSearch.Enabled = AllowSearch
'Initializing the select statement and populating the grid.
.Initialize(SelectSql, DefaultCriteria)
'Showing modally and checking for dialog result
If .ShowDialog() = Windows.Forms.DialogResult.OK Then
HashSearchResult = .HashSearchResult
Return True
Else
Return False
End If
End With
End Function
''' <summary>
''' A procedure for initializing the specified select statement and default criteria.
''' </summary>
''' <param name="SelectSql">The select statement.</param>
''' <param name="DefaultCriteria">The default criteria.</param>
Public Sub Initialize(ByVal SelectSql As String, Optional ByVal DefaultCriteria As String = "")
'Initializing the DialogResult to None
DialogResult = Windows.Forms.DialogResult.None
'Storing the SelectSql and DefaultCriteria to data members
Me.strSelect = SelectSql
Me.strCriteria = DefaultCriteria
'Open the Connection if it is not opened yet.
If CconCurrent.State <> ConnectionState.Open Then
CconCurrent.Open()
End If
'There are DefaultCriteria.
If strCriteria.Length > 0 Then
'Initializing the boolGroupBy by searching for group by clause
boolGroupBy = strSelect.ToLower.IndexOf("group by") >= 0
'It is a group by SelectSql
If boolGroupBy Then
'Initializing the strCriteriaClause as having for group by select.
strCriteriaClause = " having "
End If
'Constructing the initial Search SQL.
strSearchSelect = strSelect + strCriteriaClause + strCriteria
Else 'There are no DefaultCriteria
'Constructing the initial Search SQL without critera.
strSearchSelect = strSelect
End If
'Setting the CommandText to strSearchSelect and execute a scrollable ResultSet from it.
CcmdSearch.CommandText = strSearchSelect
CrsSearch = CcmdSearch.ExecuteResultSet(ResultSetOptions.Scrollable)
'There are row(s) returned.
If CrsSearch.Read Then
'Iterating all columns of ResultSet.
For i As Integer = 0 To CrsSearch.FieldCount - 1
'Storing columns names to combo box items.
CmbColumn.Items.Add(CrsSearch.GetName(i))
'Storing columns type names
strColumnTypes.Add(CrsSearch.GetProviderSpecificFieldType(i).ToString)
Next
End If
'Setting the combo box search column to first column.
If CmbColumn.Items.Count > 0 Then
CmbColumn.SelectedIndex = 0
End If
'Binding DataGrid to ResultSet returned.
DgSearch.DataSource = CrsSearch
'There are no incremental search yet, so empty the strSearchSelect.
strSearchSelect = ""
End Sub
''' <summary>
''' A procedure for storing a row of ResultSet in certain position to the HashTable.
''' </summary>
''' <param name="RowPosition">The row position.</param>
Sub ResultSetRowToHashTable(ByVal RowPosition As Integer)
'The row position is valid.
If RowPosition >= 0 Then
'Scrolling the ResultSet to RowPosition.
CrsSearch.ReadAbsolute(RowPosition)
'Creating an instance of HashTable.
HashSearchResult = New Hashtable
'Iterating all columns of ResultSet and storing their values to HashTable.
For i As Integer = 0 To CrsSearch.FieldCount - 1
HashSearchResult.Add(CrsSearch.GetName(i), CrsSearch.GetValue(i))
Next
'Closing and disposing the ResultSet.
If Not CrsSearch.IsClosed Then
CrsSearch.Close()
End If
CrsSearch.Dispose()
Else 'The row position is invalid.
'Triggering a cancel click.
MnuCancel_Click(Nothing, Nothing)
End If
End Sub

''' <summary>
''' Handles the DoubleClick event of the GridSearch control for storing current row to HashTable and closing the dialog.
''' </summary>
''' <param name="sender">The source of the event.</param>
''' <param name="e">The <see cref="System.EventArgs" /> instance containing the event data.</param>
Private Sub GridSearch_DoubleClick(ByVal sender As Object, ByVal e As System.EventArgs) Handles DgSearch.DoubleClick
'The grid current row index is valid.
If DgSearch.CurrentRowIndex >= 0 Then
Try
'Storing ResultSet on current row index to HashTable
ResultSetRowToHashTable(DgSearch.CurrentRowIndex)
'Returning OK dialog result and close the form.
DialogResult = Windows.Forms.DialogResult.OK
Close()
Catch ex As Exception
MsgBox(ex.Message)
End Try
End If
End Sub

''' <summary>
''' Handles the Click event of the MnuOK control for storing current row to HashTable and closing the dialog.
''' </summary>
''' <param name="sender">The source of the event.</param>
''' <param name="e">The <see cref="System.EventArgs" /> instance containing the event data.</param>
Private Sub MnuOK_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MnuOK.Click
'The grid current row index is valid.
If DgSearch.CurrentRowIndex >= 0 Then
DialogResult = Windows.Forms.DialogResult.OK
Try
'Storing ResultSet on current row index to HashTable
ResultSetRowToHashTable(DgSearch.CurrentRowIndex)
'Returning OK dialog result and close the form.
DialogResult = Windows.Forms.DialogResult.OK
Close()
Catch ex As Exception
MsgBox(ex.Message)
End Try
End If
End Sub

''' <summary>
''' Handles the Click event of the MnuCancel control for closing the dialog.
''' </summary>
''' <param name="sender">The source of the event.</param>
''' <param name="e">The <see cref="System.EventArgs" /> instance containing the event data.</param>
Private Sub MnuCancel_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MnuCancel.Click
'Closing and disposing the ResultSet.
If Not CrsSearch.IsClosed Then
CrsSearch.Close()
End If
CrsSearch.Dispose()
'Returning Cancel dialog result and close the form.
DialogResult = Windows.Forms.DialogResult.Cancel
Close()
End Sub

''' <summary>
''' Handles the Click event of the MnuSearch control for constructing incremental search.
''' </summary>
''' <param name="sender">The source of the event.</param>
''' <param name="e">The <see cref="System.EventArgs" /> instance containing the event data.</param>
Private Sub MnuSearch_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MnuSearch.Click
'There is no incremental search on going.
If strSearchSelect.Length = 0 Then
'Constructing initial select and concating with and for adding a new search criterion.
strSearchSelect = strSelect
If strCriteria.Length > 0 Then
strSearchSelect += strCriteriaClause + strCriteria + " and "
Else
strSearchSelect += strCriteriaClause
End If
Else 'There is incremental search on going.
'Concating with and for adding a new search criterion.
strSearchSelect += " and "
End If
'Current search column in combo box's type is a string.
If strColumnTypes(CmbColumn.SelectedIndex).EndsWith("String") Then
strSearchSelect += String.Format("{0} like '%{1}%'", CmbColumn.Text, TxtSearch.Text)
'Current search column in combo box's type is a DateTime.
ElseIf strColumnTypes(CmbColumn.SelectedIndex).EndsWith("DateTime") Then
strSearchSelect += String.Format("convert(nvarchar,{0},3) like '%{1}%'", CmbColumn.Text, TxtSearch.Text)
Else 'The other type of current search column in combo box.
strSearchSelect += String.Format("convert(nvarchar,{0}) like '%{1}%'", CmbColumn.Text, TxtSearch.Text)
End If
'Closing and disposing the ResultSet before retrieving new ResultSet.
If Not CrsSearch.IsClosed Then
CrsSearch.Close()
End If
CrsSearch.Dispose()
'Setting the CommandText to strSearchSelect and execute a scrollable ResultSet from it.
CcmdSearch.CommandText = strSearchSelect
CrsSearch = CcmdSearch.ExecuteResultSet(ResultSetOptions.Scrollable)
'Binding DataGrid to ResultSet returned.
DgSearch.DataSource = CrsSearch
'Focusing to search column combo box for next search.
CmbColumn.Focus()
End Sub

''' <summary>
''' Handles the Click event of the MnuReset control for resetting incremental search to initial state.
''' </summary>
''' <param name="sender">The source of the event.</param>
''' <param name="e">The <see cref="System.EventArgs" /> instance containing the event data.</param>
Private Sub MnuReset_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MnuReset.Click
'Closing and disposing the ResultSet before retrieving new ResultSet.
If Not CrsSearch.IsClosed Then
CrsSearch.Close()
End If
CrsSearch.Dispose()
'There are DefaultCriteria.
If strCriteria.Length > 0 Then
'Constructing the initial Search SQL.
strSearchSelect = strSelect + strCriteriaClause + strCriteria
Else
'Constructing the initial Search SQL.
strSearchSelect = strSelect
End If
'Setting the CommandText to strSearchSelect and execute a scrollable ResultSet from it.
CcmdSearch.CommandText = strSearchSelect
CrsSearch = CcmdSearch.ExecuteResultSet(ResultSetOptions.Scrollable)
'Binding DataGrid to ResultSet returned.
DgSearch.DataSource = CrsSearch
'There are no incremental search yet, so empty the strSearchSelect.
strSearchSelect = ""
'Empty the search text.
TxtSearch.Text = ""
End Sub

''' <summary>
''' Handles the LostFocus event of the TxtSearch control for triggering Search click.
''' </summary>
''' <param name="sender">The source of the event.</param>
''' <param name="e">The <see cref="System.EventArgs" /> instance containing the event data.</param>
Private Sub TxtSearch_LostFocus(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles TxtSearch.LostFocus
'Triggering search click.
MnuSearch_Click(Nothing, Nothing)
End Sub

''' <summary>
''' Handles the Deactivate event of the FrmSearchDialog control for triggering dialog close.
''' </summary>
''' <param name="sender">The source of the event.</param>
''' <param name="e">The <see cref="System.EventArgs" /> instance containing the event data.</param>
Private Sub FrmSearchDialog_Deactivate(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Deactivate
'The DialogResult is OK and no search result HashTable
If DialogResult = Windows.Forms.DialogResult.OK AndAlso HashSearchResult Is Nothing Then
'The grid current row index is valid.
If DgSearch.CurrentRowIndex >= 0 Then
'Triggering OK click.
MnuOK_Click(Nothing, Nothing)
Else 'Setting Cancel DialogResult.
DialogResult = Windows.Forms.DialogResult.Cancel
End If
End If
End Sub
End Class

...Read more

Tuesday, August 25, 2009

My First Journey to Mobile Database Application

My company assigns me and my team mates to automate our warehouses processes. We need to automate the manual recording of inventory flows processes.
Our first development approach is ASP.NET 2.0. This approach is the fastest to get done because we don't need to think about mobile database and server database. As long as there is wireless connection and a capable browser, the mobile devices can be used for data input. Our intended device is Motorola Symbol MC5590 with QWERTY keypad and built in barcode scanner. For development purpose we use HTC TyTN II with Windows Mobile 6.1 Pro OS. We found a China company specializing in barcodes, Sumlung and tried their software for reading a barcode through PDA camera. But their software is incompatible with our mobile device. When budget is your main consideration in developing a barcode application, you can review their solutions. We use AJAX control toolkit for automating our web pages. But to be our disappointment this ASP.NET 2.0 is failed. Our ASP.NET 2.0 website was running fine on Windows Mobile 6 Pro Emulator Images IE Mobile. But some features failed on our HTC TyTN II IE Mobile. The most obvious one is asp:Login control, the button doesn't react to a mouse click. So we find alternative browsers for our WM6. Opera Mobile can be used for running our web pages but the layout size doesn't seem right to us. Luckily we found the Iris browser. The browser can run our web pages just like we want them. Since there are no problems with software development, the project rolls onto hardware infrastructures planning. We are pretty surprised with the wireless infrastructure site survey result. Our company needs to spend a large amount of money for a full wireless LAN coverages on its warehouses. We thought an alternative from in house wireless infrastructures to unlimited HSDPA data plan from mobile telco operators. We use Hamachi Mobile from LogMeIn for connecting our mobile devices to our server. Everything seems right unless one crucial problem, Indonesia mobile telco operators are unreliable. At least there is 1 day in a week we can not connect from our mobile telco operator. We came back to wireless LAN infrastructures with reduced coverages. It seems there is no easy path on Mobile Database Application, we have to develop a .NET CF 2.0 database application.

Second approach, .NET CF 2.0 database application. First things we do are download all necessary software:
Windows Mobile 6 Pro Emulator Images (downloaded previously)
Windows Mobile 6 SDK
ActiveSync 4.5 for Windows XP (allow DMA connection for docking emulator)
Windows Mobile Device Center for Windows Vista
.NET CF 2.0 SP 2 for our Visual Studio 2005
SQL Server CE 3.1 (we download the Developer SDK and Tools for Visual Studio SP 1)
We are pretty surprised there are no significant differences between developing a desktop ADO.NET 2.0 application and a mobile ADO.NET 2.0 application. To cut the learning curve, we do not use Microsoft Synchronization Services, we use compact framework SqlClient directly to upload to and download from SQL Server 2005. There are several things about SqlCE we bumped into:
  1. Do not use ComboBox for looking up a table with a lot of records. We create a SearchDialog that utilizes a scrollable SqlCeResultSet bound to a DataGrid instead of ComboBox. The SqlCeConnection must be always connected for using SqlCeResultSet. SqlCeResultSet will only fetch shown records on DataGrid so you don't need to wait the whole records to be loaded. Our SearchDialog construct incremental search select SQL and return a HashTable of a row selected.
  2. SqlCE doesn't support update and delete using join, sub query and batch command. So we must keep this in our minds.
  3. Eliminate unneeded columns on SqlCE.
  4. Denormalize wisely for gaining performance.
  5. We use untyped dataset and it took time to load it so we serialize the dataset schema to database and deserialize it on form load. PS: We hate typed dataset :-).
  6. Our select, insert, update and delete command constructed on the fly based on table schema and it took time to load also. So we serialize and deserialize them using XML into database also.
The problem about performance and incompatible SQL make us considering several alternatives such as: SQLite, VistaDB and SSCE Direct. But time constraint makes us stay with SqlCE. For SQL Server 2005 side, all tables to be synchronized to SqlCE equipped with timestamp columns CreateTime and UpdateTime and Deleted tables for storing the deleted primary keys and their DeleteTime for each tables. We also applied these for all SqlCE tables to be uploaded to SQL Server. Records inserted in SqlCE use negative integer primary key, they will have positive integer primary key after synchronize with server.

I hope my share about our experience in developing mobile database application can be useful for all of you. Thank you.
...Read more