create an n tire app part 3

Upload: adil-bangush

Post on 10-Apr-2018

217 views

Category:

Documents


0 download

TRANSCRIPT

  • 8/8/2019 Create an N Tire App Part 3

    1/13

    15 Seconds : Using Visual Studio .NET Wizards to Create an N-Tiered

    Application - Part 3David Catherman

    11/23 / 2005

    Part 3 - Building the Business Tier

    In Part 1 and 2 of this series, we looked at using the Rapid Application Development (RAD) tools in

    Visual Studio to develop 3-tiered applications with a strong emphasis on Object Oriented Programming

    (OOP). By building Typed DataSet objects in a separate Business project and the Object Data Sources tool

    in our presentation layer, the parts of our application are isolated into tiers that can be compiled separately

    and even distributed to different servers if necessary.

    Domain Model Architecture

    While there are many different architecture models available today, there is one that fits the methodologypromoted by Microsoft Visual Studio that is a balance between relational architecture and pure object-

    oriented architecture. This architecture has recently been promoted by several renowned architects such

    as Jimmy Nilsson and is known as the Domain Model Architecture. Instead of burying the Domain layer

    deep inside the Business tier, this model elevates it to a position that allows several different layers to

    have access to the data. Figure 1 shows how the model fits with Visual Studio 2005.

    Figure 1 - Domain Model Architecture with VS2005 implementation

    This model uses the Typed DataSet as the Domain layer. The TDS wraps the DataSet object with a set of

    classes, methods and properties to allow access to data in almost a pure object oriented manner and makes

    the most of using Intellisense. In Visual Studio 2005, theses classes are contained in the generated

    Designer sub-file of the DataSet in a Partial Class. Another Partial Class is provided in a sub-file that is

    customizable by the developer and is a good place to implement business logic that affects the data.

    In another part of the Designer sub-file, separated by namespace is the Data Access logic that provides all

    the ADO code needed to retrieve and update the data from a database. The classes generated are called

    Table Adapters and are Data Adapters wrapped in strongly typed properties and methods. While these

    classes are generated, you can extend them by adding code to a Partial Class for the Table Adapters.

    When first generated, the DataSet mirrors very closely the structure or schema of the database. Some

    econds : Using Visual Studio .NET Wizards to Create an N-Tiered... http://mediakit.internet.com/icom_cgi/print/print.cgi?url=http://www.1...

    13 10/17/2010 3:44 PM

  • 8/8/2019 Create an N Tire App Part 3

    2/13

    architects complain that the DataSet is too tightly coupled with the database schema, but in reality only

    the Metadata is represented. All of the tables and fields can be renamed and mapped to better represent

    entities and columns if necessary. But if the database has been properly designed, this is not often

    necessary.

    Adding Business Layers

    Using the partial classes for business logic will work for minor code that deals directly with the data. Most

    enterprise developers prefer to have a separate class to contain the logic for each entity. I am currentlyworking with the Mere Mortals Framework by Kevin McNeish which heavily uses business objects. It is

    fairly easy to use a Typed DataSet as we have in this example while also instantiating the separate

    business object classes and pointing them to a DataTable in the existing DataSet. In his way, you get the

    best of both worldsthe power and reusability of business object classes with all the business logic and

    the data sources wizards for ease of building screens.

    Business Logic

    As we saw in Part 2 of this series, one of the important parts of business logic is providing a pass-through

    for the application to call the Data Access layer. Each Table Adapter needs to be instantiated and a

    method provided to fill the DataTable in the DataSet. For those tables that are updateable, a methodshould also be provided to pass updates back to the database. For the Northwind DataSet containing 5

    tables in the Part 2 example, here is the Data Access code needed:

    Imports ta = NorthwindDataSetTableAdapters

    Partial Public Class NorthwindDataSet

    Private taOrders As New ta.OrdersTableAdapter

    Private taOrderDetail As New ta.Order_DetailsTableAdapter

    Private taCustomer As New ta.CustomersTableAdapter

    Private taEmployee As New ta.EmployeesTableAdapter

    Private taProduct As New ta.ProductsTableAdapter

    Public Sub FillDataSetAll()

    Me.taOrders.Fill(Me.Orders)

    Me.taOrderDetail.Fill(Me.Order_Details)

    Me.taCustomer.Fill(Me.Customers)

    Me.taEmployee.Fill(Me.Employees)

    Me.taProduct.Fill(Me.Products)

    End Sub

    Public Sub UpdateOrders()

    Me.taOrders.Update(Me.Orders)

    End Sub

    Public Sub UpdateOrderDetails()

    Me.taOrderDetails.Update(Me.Order_Details)End Sub

    End Class

    This is a simplistic example where the DataTables are filled with all the records in the database. For larger

    databases, the fills can be done by passing a filter parameter.

    Parameterized Queries

    One of the options in the DataSet Designer that we have not covered yet is the ability to add multiple

    parameterized queries through which data can be retrieved from the database if you want something less

    than all the records in the table. If you open the dataset and right-click on one of the table adapter

    headers, you see a list of functions available. The Add Query option opens the Table AdapterConfiguration Wizard with the options to use SQL statements, create new stored procedures, or use

    existing stored procedures. For simple queries you can use SQL statements but in a secure environment, it

    is best to use stored procedures to access the data. The ones generated by the Wizard are adequate, but

    econds : Using Visual Studio .NET Wizards to Create an N-Tiered... http://mediakit.internet.com/icom_cgi/print/print.cgi?url=http://www.1...

    13 10/17/2010 3:44 PM

  • 8/8/2019 Create an N Tire App Part 3

    3/13

    you may use some that are generated by your DBA. If you do use existing SPs the next screen will allow

    you to select them for a list of all stored procedures in the database.

    Figure 2 - Table Adapter Command Type

    The next screen offers the choice of what the query will be used for. The options are: Select a set of rows,

    return a single value, Update, Delete, or Insert. Most often the query will be to return a set of rows, but

    the option to change the way an update or delete happens is interesting also.

    econds : Using Visual Studio .NET Wizards to Create an N-Tiered... http://mediakit.internet.com/icom_cgi/print/print.cgi?url=http://www.1...

    13 10/17/2010 3:44 PM

  • 8/8/2019 Create an N Tire App Part 3

    4/13

    Figure 3 - Table Adapter Query Type

    Each query is defined by a SQL Statement which can be typed in here or built visually using the standard

    Visual Query Builder.

    Figure 4 - Table Adapter Select Statement

    At this point you can add a filter parameter to the query to only get orders for a specific customer

    econds : Using Visual Studio .NET Wizards to Create an N-Tiered... http://mediakit.internet.com/icom_cgi/print/print.cgi?url=http://www.1...

    13 10/17/2010 3:44 PM

  • 8/8/2019 Create an N Tire App Part 3

    5/13

  • 8/8/2019 Create an N Tire App Part 3

    6/13

    the choice of several different parameters with one call. In this example, we could make one query that

    would allow the user to fill by either the CustomerID or the EmployeeID by adding to the WHERE clause

    "OR @Parameter IS NULL" as follows.

    SELECT *

    FROM Orders

    WHERE (CustomerID = @CustomerID OR @CustomerID IS NULL)

    AND (EmployeeID = @EmployeeID OR @EmployeeID IS NULL)

    AND NOT (@CustomerID IS NULL AND @EmployeeID IS NULL)

    In this example you can pass any or both parameters but if you don't pass any, you will get an empty result

    set.

    Typed DataSets handle optional parameters fairly well. The generator will create Nullable(Of Type)

    parameters for value types and correctly convert to dbnull.

    The only disadvantage with optional parameters in Typed DataSets is you have to take care of the

    parameters collection yourself. Most of the time you have to set the AllowDBNull property by hand, and

    the Wizard will reset its state every time you reconfigure the query.

    Filling a Table by Relation

    Since we have limited the number of records in the Orders table, we should also limit the rows when filling

    the Order Details table. It would be nice if Microsoft added a feature for filling by relationship, but it did

    not make it into this version. So we need to come up with a work around.

    If we were using stored procedures, we could use the Multiple Active Result Sets (MARS) feature of SQL

    Server 2005 to return the data for both tables at once. You have to add some extra code to map the result

    sets into the correct Data Tables, but it is not very difficult. Using the partial class for the Table Adapter is

    a good place to write this code since you have access to the Data Adapter object to add the

    TableMappings method.

    Figure 7 - Query to Fill Order Details by Relation

    Another approach would be to loop through the records in the Orders table and create a string of delimited

    econds : Using Visual Studio .NET Wizards to Create an N-Tiered... http://mediakit.internet.com/icom_cgi/print/print.cgi?url=http://www.1...

    13 10/17/2010 3:44 PM

  • 8/8/2019 Create an N Tire App Part 3

    7/13

    OrderIDs to pass as a parameter to be used by an IN() clause filter. But the IN() clause does note accept a

    variable, so you would have to build the SQL Statement concatenating the list of IDs into the statement

    and using an EXEC function to execute it at runtime.

    The easiest solution is to add another query to the Order Details Table Adapter that joins the tables

    together and filters on the same CompanyID as shown in the Query Builder of Figure 7. If this is the

    primary query defining the Order Details table, this will not work since it is hard for the Update to know

    which table to update. But as a secondary query, it should work fine.

    Updating Multiple Tables

    In the previous article, I showed a very simple procedure for updating the dataset back to the database.

    But the update method requires special concern when updating related tables. If the tables are updated in

    the wrong order, you could end up trying to delete parent records before child record or inserting child

    record before inserting parent records.

    The correct sequence for updating related tables is to first send the deleted children, then update the

    parent table, and then update and add the children records. The GetChanges method of the DataTable

    allows for this functionality. First we create new temporary DataTables and define them as subsets of the

    current Data Table based on which records have been added or deleted. Then each of these temporaryData Tables is updated in the correct sequence. (Note: The format for this code can be found in the VB

    Code Snippets insert.)

    Public Sub UpdateDB()

    Dim DeletedChildRecords As DataTable = _

    Me.Order_Details.GetChanges(DataRowState.Deleted)

    Dim NewChildRecords As DataTable = _

    Me.Order_Details.GetChanges(DataRowState.Added)

    Dim ModifiedChildRecords As DataTable = _

    Me.Order_Details.GetChanges(DataRowState.Modified)

    Try

    If Not DeletedChildRecords Is Nothing Then

    taOrderDetail.Update(DeletedChildRecords)

    DeletedChildRecords.Dispose()

    End If

    taOrders.Update(Me.Orders)

    If Not ModifiedChildRecords Is Nothing Then

    taOrderDetail.Update(ModifiedChildRecords)

    ModifiedChildRecords.Dispose()

    End If

    If Not NewChildRecords Is Nothing Then

    taOrderDetail.Update(NewChildRecords)

    NewChildRecords.Dispose()

    End If

    Me.AcceptChanges()

    Catch ex As ExceptionThrow ex

    End Try

    End Sub

    Adding Other Business Logic

    The partial class of the DataSet is a great place to implement any business logic that has to do with actual

    data items. We used the partial class for the dataset to instantiate the table adapters, but there are also sub

    partial classes for each of the tables (row collections), individual rows of a table, and even the row change

    event. Outside of the dataset partial class, you can also access the partial class for each of the Table

    Adapters.

    For example, if you wanted to create method that would consolidate two orders for the same customer by

    moving all the Order Detail records from one Order to another, you could create the following class inside

    the DataSet partial class (sub-class):

    econds : Using Visual Studio .NET Wizards to Create an N-Tiered... http://mediakit.internet.com/icom_cgi/print/print.cgi?url=http://www.1...

    13 10/17/2010 3:44 PM

  • 8/8/2019 Create an N Tire App Part 3

    8/13

    Partial Public Class OrdersDataSet

    ...

    Partial Public Class Order_DetailsDataTable

    Public Sub MoveDetailRecords(ByVal FromOrderID As Integer,

    ByVal ToOrderID As Integer)

    For Each row As Order_DetailsRow In Me.Select("OrderID=" & FromOrderID)

    row.OrderID = ToOrderID

    Next

    End Sub

    End Class...

    End Class

    Back in the form code the new method is accessible on the Intellisense list.

    Figure 8 - Customized Method in Intellisense

    Another example might be the need for a method that will copy the address information from the

    customer to the shipping section of the order.

    Partial Public Class OrdersRow

    Public Sub ShipToCustomer()

    If Me.CustomerID Is Nothing Then

    MsgBox("Customer not defined") 'TODO: should throw an exception

    ReturnEnd If

    Dim CustTable As CustomersDataTable = Me.Table.DataSet.Tables("Customers")

    Dim CustRow As CustomersRow = CustTable.FindByCustomerID(Me.CustomerID)

    Me.ShipName = CustRow.ContactName

    Me.ShipAddress = CustRow.Address

    Me.ShipCity = CustRow.City

    Me.ShipRegion = CustRow.Region

    Me.ShipPostalCode = CustRow.PostalCode

    Me.ShipCountry = CustRow.Country

    End Sub

    End Class

    Back in the form code, when you get a reference to an OrderRow object, the method is available to copy

    the shipping address.

    These are just a couple examples of how to build business logic in the partial class of the DataSet. The

    advantage is that all of the DataSet objects are available in context. If you were to create another specific

    class as your business object, you would always have to pass a reference to the dataset to access the data

    objects. In the above examples, the data object can be referenced as "Me" since the coding is inside the

    dataset object.

    Using Macros to Generate Data Access Code

    Since the code for the Data Access layer is very repetitive, varying only by the table name, this is a good

    place for code generation. There are many different code generators available, each with their strengths

    and short comings. Recently I have been investigating using macros to generate code. The advantage to

    this method is that the generation is done in the context of the IDE. All of the formatting, indentation, and

    End tags are inserted just like when you are typing the code manually.

    econds : Using Visual Studio .NET Wizards to Create an N-Tiered... http://mediakit.internet.com/icom_cgi/print/print.cgi?url=http://www.1...

    13 10/17/2010 3:44 PM

  • 8/8/2019 Create an N Tire App Part 3

    9/13

    To create a macro, from the VS top line menu select Tools / Macros / Macros IDE. This will open another

    IDE similar to VS for editing macros. In the class list, right click on MyMacros and add a new class (or

    Module) called GenDataAccessLayer. Inside of the new class create the following method.

    The following macro uses reflection to look into the dataset, instantiate each table adapter and call the fill

    method of each. The following namespaces need to be included:

    Imports EnvDTE

    Imports EnvDTE80Imports System.Diagnostics

    Imports System.Reflection

    Imports System

    In a macro, the Visual Studio integrated development environment (DTE) contains a pointer to the active

    document which points to the item from the Solution Explorer that is open. The item also knows which

    project it is in.

    Public Sub DefineTAviaReflectionVB()

    Dim pItem As ProjectItem = DTE.ActiveDocument.ProjectItem

    Dim ItemName As String = pItem.Name.Substring(0, pItem.Name.LastIndexOf("."))

    Dim proj As Project = pItem.ContainingProject

    Dim line As String = ""Dim TableName As String

    Dim taList As String

    Dim UpdateList As String = ""

    Dim UpdateType As MethodInfo

    From the full project name, we can build the location of the actual assembly. If you are looking for a

    Windows Forms assembly rather than the Business class library, you should use the .EXE instead of the

    .DLL extension.

    ProjName = ProjName.Substring(0, ProjName.LastIndexOf("\") + 1)

    ProjName = ProjName & "bin\debug\" & proj.Name & ".dll"

    Once you have the assembly, you can load the assembly from the file. There should be a better way to get

    a reference to the assembly from the project itself, but I have not yet found one.

    Dim targetAssembly As Assembly = Assembly.LoadFrom(ProjName)

    Now loop through each Type in the assembly, looking for those in the group of dataset table adapters

    ending in "TableAdapter".

    For Each typ As Type In targetAssembly.GetTypes()

    If typ.FullName Like "*" & ItemName & "TableAdapters." & _

    "*TableAdapter" Then

    TableName = typ.Name.Substring(0, _

    typ.Name.LastIndexOf("TableAdapter"))

    Here is where the line of code actually gets built.

    line = "Dim ta" & TableName & " As " & ItemName & _

    "TableAdapters." & typ.Name

    DTE.ActiveDocument.Selection.NewLine()

    DTE.ActiveDocument.Selection.Text = line

    Save the object name for building the fill methods later.

    taList &= "ta" & TableName & ","

    Check if the table adapter has an update method (actually it is easier to check for the Delete method) and

    store the table adapter for later use.

    UpdateType = typ.GetMethod("Delete")

    econds : Using Visual Studio .NET Wizards to Create an N-Tiered... http://mediakit.internet.com/icom_cgi/print/print.cgi?url=http://www.1...

    13 10/17/2010 3:44 PM

  • 8/8/2019 Create an N Tire App Part 3

    10/13

    If UpdateType IsNot Nothing Then

    UpdateList &= "ta" & TableName & ","

    End If

    Next

    Now build a method to call the fill method for each table adapter.

    DTE.ActiveDocument.Selection.EndOfLine()

    DTE.ActiveDocument.Selection.NewLine()

    'Add the Update methodsFor Each ta As String In UpdateList.Split(",")

    If ta.Length > 0 Then

    DTE.ActiveDocument.Selection.Text = "Public Sub Update" + _

    ta.Substring(2) + "()"

    DTE.ActiveDocument.Selection.NewLine()

    DTE.ActiveDocument.Selection.Text = ta & ".Update(" & _

    ta.Substring(2) & ");"

    DTE.ActiveDocument.Selection.NewLine()

    End If

    Next

    targetAssembly = Nothing

    End Sub

    This code applied to our Northwind example generates the following code:

    Partial Class NorthwindDataSet

    Dim taCustomers As New NorthwindDataSetTableAdapters.CustomersTableAdapter

    Dim taEmployees As New NorthwindDataSetTableAdapters.EmployeesTableAdapter

    Dim taOrder_Details As New NorthwindDataSetTableAdapters.Order_DetailsTableAdapter

    Dim taOrders As New NorthwindDataSetTableAdapters.OrdersTableAdapter

    Dim taProducts As New NorthwindDataSetTableAdapters.ProductsTableAdapter

    Public Sub FillAll()

    taCustomers.Fill(Customers)

    taEmployees.Fill(Employees)

    taOrder_Details.Fill(Order_Details)

    taOrders.Fill(Orders)taProducts.Fill(Products)

    End Sub

    Public Sub UpdateOrder_Details()

    taOrder_Details.Update(Order_Details)

    End Sub

    Public Sub UpdateOrders()

    taOrders.Update(Orders)

    End Sub

    End Class

    I would appreciate some feedback on the logic here. Is it better to leave the table adapter instantiated for

    the life of the dataset (is there a large memory hit for this object), or would it be better to instantiate it

    each time it is needed (what is the processor requirement to instantiate this object) and free up thememory. In this case, I chose to leave them instantiated so they would be available for the update call.

    Database Provider Independence

    One thing Microsoft did not get added to the Table Adapter logic is to take advantage of the Provider

    Pattern to allow switching between different back end database engines. The Provider Pattern is a

    combination of the Abstract Factory Pattern and a couple others to streamline the ability to switch

    between different back-ends without recompiling the application. This pattern is available in DotNetNuke,

    ASP.NET and a few other Microsoft technologies, but they did not have time to get it to work with the

    generated Table Adapters. Since I still have a need to develop an application that can be database

    independent, I have continued to pursue a strategy for accomplishing the feat.

    In my first attempt, I tried to change the connection string to a different source and then regenerate the

    dataset. This did not work because the dataset generator trying to use the old ADO connection type. But

    econds : Using Visual Studio .NET Wizards to Create an N-Tiered... http://mediakit.internet.com/icom_cgi/print/print.cgi?url=http://www.1...

    f 13 10/17/2010 3:44 PM

  • 8/8/2019 Create an N Tire App Part 3

    11/13

    you can create another connection to the other database and inside the dataset generator, change which

    connection each table points to and then regenerate the table.

    To continue with our Northwinds example, the sample database from Microsoft is available in MS Access,

    FoxPro, and SQL Server. To demonstrate this procedure, I will convert our sample application so that it

    will run against the Access database as well as the SQL Server database. Where this is less complicated if

    the OLEDB provider were originally used for the SQL Server connection, this demonstration will use

    OLEDB provider for the Access database and continue to use the SQLClient provider for SQL Server.

    Our sample application currently is configured to work with the SQL Server connection. Before we

    change that to an Access Connection, let's save the current configuration so we can easily come back to it.

    Open the DataSet and open the Designer code file. (You may have to turn on the "Show All Files" option

    at the top of Solution Explorer in order to see the sub files under the DataSet.) There are two main

    sections inside the Designer file: the Partial Public Class OrdersDataSet and the Namespace

    OrdersDataSetTableAdapters. There are several rows of attributes decorating the class, but if you collapse

    the section by clicking the minus sign in the margin, you should see the two sections.

    Figure 9 - DataSet Designer code file

    If you highlight the Table Adapters section and press Ctrl+C, you are copying the whole section. Now

    create a new Class module in the project, name it OrdersDataSetTableAdapters.SQL and paste the copied

    code into it. Of course this will generate errors because of the duplication, but if we change the

    Namespace by adding a ".SQL" on the end of it, it should compile fine.

    The next step is to go back to the DataSet Designer for the Orders DataSet, right click on the Orders table

    and select Configure.... The Wizard starts on page 3, but you can click the Previous button twice to get

    back to the first page where the connection is defined. Select the connection for the Access database or

    click the New Connection button to create a new one. By adding a connection here, it will be recorded in

    Project settings and in the App.Config file.

    After changing the connection string, proceed through and finish the Configuration Wizard. The Table

    Adapter has now been changed to point to the Access database.

    econds : Using Visual Studio .NET Wizards to Create an N-Tiered... http://mediakit.internet.com/icom_cgi/print/print.cgi?url=http://www.1...

    f 13 10/17/2010 3:44 PM

  • 8/8/2019 Create an N Tire App Part 3

    12/13

    Figure 10 - Changing Table Adapter Connections

    Continue this procedure for each table in the dataset and you will have converted the application to now

    use the Access database instead of SQL Server. You should be able to run the application and see the

    Access data. If you have doubts, you can try changing some of the information and then look into both the

    Access and SQL Server tables to see where it has changed.

    The next step is to capture the Table Adapters section from the Designer file and create another Class

    module Called OrderDataSetTableAdapters.Jet and paste in the code. Change the Namespace this time by

    adding a ".Jet" on the end of it.

    Now we can switch back and forth between Access and SQL Server simply by changing the extension of

    the Namespace on the Imports statement at the top of the Developers partial class file. Adding a ".Jet"

    points the table adapters to the code saved for connecting to Access.

    Imports ta = NorthwindDataSetTableAdapters.Jet

    Partial Public Class NorthwindDataSet

    Private taOrders As New ta.OrdersTableAdapter

    Private taOrderDetail As New ta.Order_DetailsTableAdapter

    ...

    While this is a large step in the right direction, it is not the final solution because you still have to build the

    project after changing the extension. In a true Provider Pattern, you should be able to make the change

    simply by changing a parameter in the App.Config file and never have to recompile. I plan to continue

    working on the problem and will probably come back with a solution in a future article. If any of you

    come up with an idea, please let me know.

    Conclusion

    I am still very impressed with Visual Studio 2005 and the advances made in developing data applicationsrapidly. I am finally using the actual release bits and all of this development done during the Beta cycle

    still works. One interesting change is the new attributes added to each class in the Designer code. One of

    the problems of working with Table Adapters is that they are not inherited from any base class nor do they

    econds : Using Visual Studio .NET Wizards to Create an N-Tiered... http://mediakit.internet.com/icom_cgi/print/print.cgi?url=http://www.1...

    f 13 10/17/2010 3:44 PM

  • 8/8/2019 Create an N Tire App Part 3

    13/13

    implement any interface making it hard to apply object oriented techniques to them. The inclusion of the

    attributes should provide the links necessary to work with using CodeDom and reflection. Look for future

    articles as I figure out what is going on.

    About the Author

    David Catherman - CMI Solutions

    Email: DCatherman (at) CMiSolutions (dot) com

    David Catherman has 20+ years designing and developing database applications with

    specific concentration for the last 4-5 years on Microsoft .NET and SQL Server. He is

    currently Application Architect and Senior Developer at CMI Solutions using Visual

    Studio and SQL Server 2005. He has 3 MCP certifications in .NET and is pursuing MCSD.

    Back to article

    Copyright 2005 Jupitermedia Corp. All Rights Reserved.Legal Notices, Licensing, Reprints, & Permissions, Privacy Policy.

    http://www.internet.com

    econds : Using Visual Studio .NET Wizards to Create an N-Tiered... http://mediakit.internet.com/icom_cgi/print/print.cgi?url=http://www.1...