create an n tire app part 3
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...