asp tutorials, asp.net tutorials, sample code, and Microsoft news from 15Seconds
Data Access  |   Troubleshooting  |   Security  |   Performance  |   ADSI  |   Upload  |   Email  |   Control Building  |   Component Building  |   Forms  |   XML  |   Web Services  |   ASP.NET  |   .NET Features  |   .NET 2.0  |   App Development  |   App Architecture  |   IIS  |   Wireless
 
Pioneering Active Server
 Power Search










Active News
15 Seconds Weekly Newsletter
• Complete Coverage
• Site Updates
• Upcoming Features

More Free Newsletters
Reference
News
Articles
Archive
Writers
Code Samples
Components
Tools
FAQ
Feedback
Books
Links
DL Archives
Community
Messageboard
List Servers
Mailing List
WebHosts
Consultants
Tech Jobs
15 Seconds
Home
Site Map
Press
Legal
Privacy Policy
internet.commerce














internet.com
IT
Developer
Internet News
Small Business
Personal Technology
International

Search internet.com
Advertise
Corporate Info
Newsletters
Tech Jobs
E-mail Offers

HardwareCentral
Compare products, prices, and stores at Hardware Central!

Designing N-Tiered Data Access Layer Using Datasets - Part 3
By David Catherman
Rating: 4.7 out of 5
Rate this article


  • email this article to a colleague
  • suggest an article

    Part 3 - Generating the Data Access Code

    Introduction

    The Dataset Editor in Visual Studio 2005/8 assists in the design of a data entity structure and generates a set of classes that meets the requirements of a good object relation mapper (ORM). This article shows the code that needs to be added to provide tier separation and how it can easily be generated automatically.

    Overview

    Part 1 of this article covered the basics of the Typed DataSet in Visual Studio 2005/8, including a quick tutorial on how to use the visual editor / wizards and a review of the functionality of the code it generates. Part 2 showed how to use partial classes to wrap the table adapter methods in the data table class as well as extending the functionality of the table adapter.

    The code to wrap the table adapter methods is very repetitive and be easily generated using either the schema as metadata or through reflection into the assembly.

    Code Generation Fundamentals

    There are many code generation tools on the market. CodeSmith (by CodeSmith Tools, LLC) is well know and fairly inexpensive. MyGeneration is an open source tool that is highly rated and free. Both tools do a good job of code generation and even ORM. They both will build a template model by reading the metadata from a database, but I could not get them to read the metadata from a dataset. So I ended up writing my own generator.

    The first step in code generation is to collect metadata about your model and then build a template that will model the resulting code with variables where the code varies. The generator then takes the data from the metadata and plugs it into the templates to output the resultant code.

    In this case the metadata has been collected by the dataset wizards and is stored in an XML schema file known as an XSD. The XML contains all the information about each table and relation and is quite extensive. To see the raw XML, right click on the ".xsd" dataset file and select "Open With" and "XML Editor" or open the file in any XML Editor.

    There are two sections: the Annotation node contains information about the table adapters and the Element section contains information about the tables and relationships. Parsing this XML would collect all the metadata needed to generate the code.

    Another way to collect the metadata is to use reflection to look into the compiled assemble (.dll file) and read the structure of the classes and properties. I found this to be a bit easier.

    Building the Front End

    I used a Windows application to build a simple front end to allow interaction with the developer. The form has several unbound fields, a couple text boxes, list boxes, and buttons.

    The first step is to identify the assembly file of the desired project where the dataset was created and compiled (need to hit the build button to create the assembly). Behind the ellipse button is an Open file dialog component that allows the user to select the desired assembly. (It is possible to locate the assembly by namespace, but there were some problems associated, so I went back to the "Load from File" method.)

    While in development, the file lives in the Debug folder under the project's \bin folder. In an n-tiered environment, the dataset should be in a class library project which has a ".dll" extension, but if the dataset was added to a Windows forms project, you would be looking for and ".exe" extension. This distinction makes it a little harder to automate the assembly lookup from just the project name, so it is safer to let the user identify the file.

    Once the assembly has been identified, the following code will open it in reflection and populate the first list box with a list of all the dataset classes found.

    Imports System.Reflection

    Public Class DatasetSelectForm

        Private Sub OpenFileButton_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles OpenFileButton.Click
            Me.OpenFileDialog1.InitialDirectory = My.Computer.FileSystem.CurrentDirectory
            Me.OpenFileDialog1.ShowDialog()
            Me.TextBox1.Text = Me.OpenFileDialog1.FileName
            ReadTypes(Me.TextBox1.Text)
        End Sub

        Private Sub ReadTypes(ByVal FileName As String)
            Me.ListBox1.Items.Clear()
            Dim proj As Assembly = Assembly.LoadFile(FileName)
            With proj
                For Each cls As Type In proj.GetTypes
                    'Look for classes that inherit from Dataset
                    If cls.BaseType IsNot Nothing AndAlso cls.BaseType.Name = "DataSet" Then
                        Me.ListBox1.Items.Add(cls.Name)
                    End If
                Next
            End With
        End Sub

    Once the list is completed, control is passed back to the user who will select one of the datasets in the list. On selection, the application will read the dataset class and populate the second list box with the table adapters in the dataset to verify to the user that the correct dataset has been selected.

        Private Sub ListBox1_SelectedIndexChanged(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles ListBox1.SelectedIndexChanged
            ListTables(ListBox1.SelectedItem)
        End Sub

        Private Sub ListTables(ByVal dsName As String)
            Me.ListBox2.Items.Clear()
            Dim proj As Assembly = Assembly.LoadFile(Me.TextBox1.Text)
            With proj
                For Each cls As Type In proj.GetTypes
                    'look for namespace match since there is no base type
                    If cls.FullName Like "*" & dsName & "TableAdapters.*TableAdapter" Then
                        'If cls.(Global.System.Data.DataSet) IsNot Nothing Then
                        Me.ListBox2.Items.Add(cls.Name)
                    End If
                Next
            End With
        End Sub

    Now the metadata has been collected, it is time to generate the code. Not every dataset will need the ability to interact with the adapter, so I left it as options to just generate the data table methods or both the data table and the table adapter methods. When the user clicks a button, the corresponding subroutines are called.

        Private Sub TableButton_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles TableButton.Click
            GenDATables(ListBox1.SelectedItem)
            Clipboard.SetDataObject(Me.CodeTextBox.Text)
        End Sub

        Private Sub BothButton_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles BothButton.Click
            GenDATables(ListBox1.SelectedItem)
            GenTableAdapterExt(ListBox1.SelectedItem)
            Clipboard.SetDataObject(Me.CodeTextBox.Text)
        End Sub

    The called functions will generate the code and place the resulting code in the text box on the form and also copy it to the clipboard so it can be pasted into the custom code page of the dataset.

    The first function generates the data table code. When I wrote this code, I did not take the time to make it pretty. I am sure this code can be improved in many ways, not the least of which is to use string builder instead of concatenation, but it still executes very quickly--less than one second for a very large dataset.

        Private Sub GenDATables(ByVal dsName As String)
            Dim proj As Assembly = Assembly.LoadFile(Me.TextBox1.Text)
            Dim ta As String 'Table adapter name
            Dim t1, t2, t3 As String
            Dim cr As String = Chr(13) & Chr(10)
            Dim parList As String

            With proj
                'Instantiate the table adapters
                t1 = "'#### The following Code is generated and should not be edited ###" & cr & cr
                t1 += "Partial Class " & dsName & cr & cr
                'create DataTable sub classes
                t2 = "#Region ""Table Sub Classes""" & cr & cr
                'Fill all method
                t3 = "Public Sub FillAll()" & cr
                t3 += "Try" & cr

                For Each cls As Type In proj.GetTypes
                    'look for namespace match since there is no base type for tableadapters
                    If cls.FullName Like "*" & dsName & "TableAdapters.*TableAdapter" Then
                        ta = cls.Name.Substring(0, cls.Name.LastIndexOf("TableAdapter"))
                        'instantiate the table adapters
                        t1 += "Shared ta" & ta & " As New " & dsName & "TableAdapters." & cls.Name & cr

                        'Build the table class
                        t2 += cr
                        t2 += "Partial Public Class " & ta & "DataTable" & cr
                        t2 += "Implements _Interface.ITableUpdate" & cr & cr

                        'Fill method
                        t2 += "Public Sub Fill() Implements _Interface.ITableUpdate.Fill" & cr
                        t2 += "Try" & cr
                        t2 += "ta" & ta & ".Fill(Me)" & cr
                        t2 += "Catch ex As Exception" & cr
                        t2 += "Throw New ApplicationException(""" & ta & ": Fill - "" & ex.Message)" & cr
                        t2 += "End Try" & cr
                        t2 += "End Sub" & cr & cr

                        'Fill (IgnoreException) function
                        t2 += "Public Function Fill(ByVal IgnoreErrors As Boolean) As String" & cr
                        t2 += "Try" & cr
                        t2 += "Return """ & ta & "="" & ta" & ta & ".Fill(Me)" & cr
                        t2 += "Catch ex As Exception" & cr
                        t2 += "If IgnoreErrors Then" & cr
                        t2 += "Return """ & ta & ": Fill - "" & ex.Message" & cr
                        t2 += "Else" & cr
                        t2 += "Throw New ApplicationException(""" & ta & ": Fill - "" & ex.Message)" & cr
                        t2 += "End If" & cr
                        t2 += "End Try" & cr
                        t2 += "End Sub" & cr & cr

                        'Any other FillBy methods
                        parList = ""
                        For Each meth As MethodInfo In cls.GetMethods()
                            If meth.Name Like "FillBy*" Then
                                t2 += "Public Sub " & meth.Name & "("
                                parList = ""
                                For Each pi As ParameterInfo In meth.GetParameters()
                                    If pi.Name <> "dataTable" Then
                                        If parList.Length > 0 Then
                                            t2 += ","
                                            parList += ","
                                        End If
                                        t2 += "ByVal " & pi.Name & " As " & pi.ParameterType.ToString
                                        parList += pi.Name
                                    End If
                                Next
                                t2 += ")" & cr
                                t2 += "Try" & cr
                                t2 += "ta" & ta & "." & meth.Name & "(Me," & parList & ")" & cr
                                t2 += "Catch ex As Exception" & cr
                                t2 += "Throw New ApplicationException(""" & ta & ": " & meth.Name & " - "" & ex.Message)" & cr
                                t2 += "End Try" & cr
                                t2 += "End Sub" & cr & cr
                            End If
                        Next

                        'Update Method
                        t2 += "Public Sub Update() Implements _Interface.ITableUpdate.Update" & cr
                        If cls.GetMethod("Delete") IsNot Nothing Then 'Does adapter allow for update
                            t2 += "Biz.Utility.UpdateModifiedFields(Me)" & cr
                            t2 += "Try" & cr
                            t2 += "ta" & ta & ".Update(Me)" & cr
                            t2 += "Catch ex As Exception " & cr
                            t2 += "Throw New ApplicationException(""" & ta & ": Update - "" & ex.Message)" & cr
                            t2 += "End Try" & cr
                        Else
                            t2 += "Throw New ApplicationException(""" & ta & " Table is read only - cannot update"")" & cr
                        End If
                        t2 += "End Sub" & cr
                        t2 += "End Class" & cr & cr
                        'Fill all method
                        t3 += ta & ".Fill()" & cr
                    End If

                Next
                t1 += cr 'space before #region

                t2 += "#End Region" & cr & cr

                t3 += "Catch ex As Exception" & cr
                t3 += "Throw New ApplicationException(""" & dsName & ": FillAll - "" & ex.Message)" & cr
                t3 += "End Try" & cr
                t3 += "End Sub" & cr & cr
                t3 += "End Class" & cr & cr
            End With

            proj = Nothing
            CodeTextBox.Text = t1 & t2 & t3
        End Sub

    There are three different places in the code where the list of table adapters needs to be looped through. Rather than loop through the reflection three times, I created three different string variables and then concatenated them together at the end.

    One section to note is where the code loops through each method to see if there are any extra queries added to the table adapter. Even though the dataset wizard allows the user to change the name of the methods, this code counts on the user sticking with the default Fill and Update names. In the case of additional queries, use a descriptive name, but make sure to keep the default "FillBy" prefix to the name.

    The second function builds the code to extend the table adapters. A separate partial class is used so the previously generated code can stand alone if this code is not generated.

        Public Sub GenTableAdapterExt(ByVal dsName As String)
            Dim proj As Assembly = Assembly.LoadFile(Me.TextBox1.Text)
            Dim ta As String 'Table adapter name
            Dim t1, t2, t3 As String
            Dim cr As String = Chr(13) & Chr(10)

            With proj

                t1 = "'Implement Table Adapter Extentions" & cr
                t1 += "Partial Class " & dsName & cr
                t1 += "Implements _Interface.ITypedDataSet" & cr & cr
                t1 += "Private _CurrentUser As String" & cr
                t1 += "'String value for the current username to put in the ModifiedBy field" & cr
                t1 += "Public Property CurrentUser() As String Implements _Interface.ITypedDataSet.CurrentUser" & cr
                t1 += "Get" & cr
                t1 += "Return _CurrentUser" & cr
                t1 += "End Get" & cr
                t1 += "Set(ByVal value As String)" & cr
                t1 += "_CurrentUser = value" & cr
                t1 += "End Set" & cr
                t1 += "End Property" & cr & cr

                'GetAdapter
                t2 = "Public Function GetAdapter(ByVal TableName As String) As Global.System.Data.SqlClient.SqlDataAdapter Implements _Interface.ITypedDataSet.GetAdapter" & cr
                t2 += "Select Case TableName" & cr

                'Table(adapters)
                t3 = "'Table Adapter extentions to expose the Adapter as public" & cr
                t3 += "Namespace " & dsName & "TableAdapters" & cr & cr

                For Each cls As Type In proj.GetTypes
                    'look for namespace match since there is no base type
                    If cls.FullName Like "*" & dsName & "TableAdapters.*TableAdapter" Then
                        ta = cls.Name.Substring(0, cls.Name.LastIndexOf("TableAdapter"))
                        t2 += "Case """ & ta & """" & cr 'sys_ErrorLog"
                        t2 += "Return ta" & ta & ".GetAdapter()" & cr

                        t3 += "Partial Public Class " & ta & "TableAdapter" & cr
                        t3 += "Public Function GetAdapter() As Global.System.Data.SqlClient.SqlDataAdapter" & cr
                        t3 += "If (Me._adapter Is Nothing) Then" & cr
                        t3 += "Me.InitAdapter()" & cr
                        t3 += "End If" & cr
                        t3 += "Me.Adapter.SelectCommand = Me.CommandCollection(0)" & cr
                        t3 += "Return Me._adapter" & cr
                        t3 += "End Sub" & cr
                        t3 += "End Class" & cr & cr
                    End If
                Next
            End With

            t2 += "Case Else" & cr
            t2 += "Return Nothing" & cr
            t2 += "End Select" & cr
            t2 += "End Sub" & cr & cr
            t2 += "End Class" & cr & cr

            t3 += "End Namespace" & cr
            Proj = nothing
            Me.CodeTextBox.Text += t1 & t2 & t3

        End Sub

    Running this code will generate the code found in the Appendix of Part 2 of this article. After running the application and generating the code, switch back to the copy of Visual Studio with the project containing the dataset and open the custom code file in the dataset (open the dataset and right-click, View Code). Leave the partial class stub at the top to add custom code to the dataset and move the cursor to the bottom of the file and paste (Ctrl-V) the code. The code will automatically format with the proper indentations.

    Separating the Generated Code

    The above method is a bit cumbersome in that each dataset needs to be opened to paste the generated code. Another problem is that the developer may be tempted to edit the generated code. A solution to this problem is to put the generated code in another file. The dataset adds the custom code file as a sub file to the dataset, but the partial class will work no matter where the code is located in the same project.

    Right click on the project in Solution Explorer and click "Add", and "Class" and give it a name like GeneratedDAL.vb. Visual Studio will create a class stub that you can delete.

    The last button on the Code Generation form will generate the Data Access code for all datasets in the project. Clicking this button will run the following code:

        Private Sub GenAllButton_Click(ByVal sender As System.Object, _
                ByVal e As System.EventArgs) Handles GenAllButton.Click
            Dim all As String = ""
            For Each ds As String In Me.ListBox1.Items
                Me.CodeTextBox.Text = ""
                GenDATables(ds)
                GenTableAdapterExt(ds)
                all += Me.CodeTextBox.Text & Chr(13) & Chr(10)
            Next
            Clipboard.SetDataObject(all)
            MessageBox.Show("Generated code has been placed on the clipboard")
        End Sub
    End Class

    After running this code, switch back to the newly created empty class in Visual Studio and paste (Ctrl-V) the code. When you compile, the partial classes will be combined with the partial classes created by the dataset generator into a single assembly. Opening the class in Object Viewer will show the added methods and properties.

    Conclusion

    Code Generation saves a lot of time in application development. In this simplest of business objects, around 3000 lines of code was generated by Visual Studio and another 150 lines of code was generated by this custom generator. In a very large dataset with 16 tables that is 16000 and 1000 lines of code respectively.

    If you have never used an ORM tool, the dataset editor is a good way to get started, but it might spoil you since there are not many products on the market with as good of a visual editor.

    When building an N-tiered application, it is important to start with a solid set of business objects that accurately model the functionality to drive the presentation layer. Applying the Model View Controller (MVC) architectural pattern, the business object provides the model that is represented in the view. In future articles, I hope to continue this theme.

    About The Author

    David Catherman

    Email: David (dot) Catherman (at) Hotmail (dot) com

    David Catherman has 20+ years designing and developing database applications with specific concentration for the last 5-6 years on Microsoft .NET and SQL Server. He is currently working as an Application Architect and Senior Developer using Visual Studio 2005 and SQL Server 2005. He has several MCPs in .NET and is pursuing MCSD.

  • Rate This Article
    Not HelpfulMost Helpful
    1 2 3 4 5
    Other Articles
    Jul 21, 2005 - N-Tier Web Applications using ASP.NET 2.0 and SQL Server 2005 - Part 1
    While the .NET Framework made building ASP.NET applications easier then it had ever been in the past, .NET 2.0 builds on that foundation in order to take things to the next level. This article shows you to how to construct an N-Tier ASP.NET 2.0 Web application by leveraging the new features of ASP.NET 2.0 and SQL Server 2005.
    [Read This Article]  [Top]
    Apr 28, 2005 - New Files and Folders in ASP.NET 2.0
    With the release of ASP.NET 2.0, Microsoft has greatly increased the power of ASP.NET by introducing a suite of new features and functionalities. As part of this release, ASP.NET 2.0 also comes with a host of new special files and folders that are meant to be used to implement a specific functionality. This article examines these new files and folders in detail and provides examples that demonstrate how to utilize them to create ASP.NET 2.0 applications.
    [Read This Article]  [Top]
    Mar 10, 2005 - The DataSet Grows Up in ADO.NET 2.0 - Part 2, Cont'd
    Alex Homer continues his detailed look at the major changes to the DataSet class. In this part, he looks at two features that allow developers to work with data in a more structured and efficient way when using the DataSet with a SQL Server 2005 database server.
    [Read This Article]  [Top]
    Mar 9, 2005 - The DataSet Grows Up in ADO.NET 2.0 - Part 2
    Alex Homer continues his detailed look at the major changes to the DataSet class. In this part, he looks at two features that allow developers to work with data in a more structured and efficient way when using the DataSet with a SQL Server 2005 database server.
    [Read This Article]  [Top]
    Mar 3, 2005 - The DataSet Grows Up in ADO.NET 2.0 - Part 1, Cont'd
    In this article, Alex Homer looks at the changes between the version 1.x and version 2.0 DataSet and their associated classes, showing you how you can take advantage of the new features to improve your applications' capabilities and performance.
    [Read This Article]  [Top]
    Mar 2, 2005 - The DataSet Grows Up in ADO.NET 2.0 - Part 1
    In this article, Alex Homer looks at the changes between the version 1.x and version 2.0 DataSet and their associated classes, showing you how you can take advantage of the new features to improve your applications' capabilities and performance.
    [Read This Article]  [Top]
    Feb 16, 2005 - Writing a Custom Membership Provider for the Login Control in ASP.NET 2.0
    In ASP.NET 2.0 and Visual Studio 2005, you can quickly program custom authentication pages with the provided Membership Login controls. In this article, Dina Fleet Berry examines the steps involved in using the Login control with a custom SQL Server membership database.
    [Read This Article]  [Top]
    Dec 29, 2004 - ClickOnce Deployment in .NET Framework 2.0
    In this article, Thiru Thangarathinam examines .NET 2.0's new ClickOnce deployment technology that is designed to ease deployment of Windows forms applications. This new technology not only provides an easy application installation mechanism, it also eases deployment of upgrades to existing applications.
    [Read This Article]  [Top]
    Dec 15, 2004 - A Sneak Peek at ASP.NET 2.0's Administrative Tools
    With ASP.NET 2.0, Microsoft has made great strides in increasing developer productivity and has made implementing previously complex solutions relatively easy. Where this version of ASP.NET really shines, however, is in its new administrative tools that allow developers to spend less time managing the configuration of the servers and software and more time developing great code.
    [Read This Article]  [Top]
    Nov 17, 2004 - The ASP.NET 2.0 TreeView Control
    Thiru Thangarathinam introduces ASP.NET 2.0's new TreeView control which provides a seamless way to consume and display information from hierarchical data sources. The article discusses this new control in depth and explains how to use this feature rich control in your ASP.NET applications.
    [Read This Article]  [Top]
    Mailing List
    Want to receive email when the next article is published? Just Click Here to sign up.

    Support the Active Server Industry



    JupiterOnlineMedia

    internet.comearthweb.comDevx.commediabistro.comGraphics.com

    Search:

    Jupitermedia Corporation has two divisions: Jupiterimages and JupiterOnlineMedia

    Jupitermedia Corporate Info


    Legal Notices, Licensing, Reprints, & Permissions, Privacy Policy.

    Advertise | Newsletters | Tech Jobs | Shopping | E-mail Offers