Design. NET Application data Access layer Five principles

Source: Internet
Author: User
Tags abstract object exception handling connect ole variable tostring access
Program | access | design | data

Summary: most use. NET Framework component is a core task for developers who work with data access capabilities, and the data access layer they create is an essential part of the application's layer. This article outlines the use of visual Studio. NET and. NET Framework components to build a data access layer five ideas to consider. These techniques include the use of object-oriented techniques and using base classes (base class). NET Framework component infrastructure that makes classes easy to inherit, examining requirements carefully before deciding on display methods and outside boundaries.

If you are building a data-centric (data-centric). NET Framework component application, you eventually have to establish a data access layer. Maybe you know where. NET Framework component, there are many benefits to building your own code. Because it supports implementation and interface (interface) inheritance, your code is easier to reuse, especially for developers who use different framework components compatible (framework-compliant) languages. This article I will outline as based on. NET Framework component's application to establish a data access layer five rules.

Before I begin, I must remind you that any data access layer you establish that is based on the rules discussed in this article must be compatible with multi-tier or n-tier applications that developers on the traditional Windows platform prefer. In this structure, the presentation layer contains the XML service code for Web Forms, Windows Forms, and the transaction layer that invokes the work of the data access layer. This layer is composed of multiple data access classes (classe). In other words, when transactional coordination is not necessary, the presentation layer invokes the data access layer directly. This structure is a variant of the traditional model-View list-control program (MODEL-VIEW-CONTROLLER,MVC) pattern, which in many cases is visual Studio. NET and the controls it exposes.

Rule 1: Using object-oriented attributes

The most basic object-oriented transaction is to create an abstract class that uses implementation inheritance. This base class can include all of your data access classes by inheriting the services that you can use. If those services are sufficient, they can be reused over the entire organization's base class distribution. For example, the simplest scenario is that a base class can handle the build process of a connection for a derived class, as shown in Listing 1.

Imports System.Data.SqlClient

Namespace ACME. Data
Public MustInherit Class dalbase:implements IDisposable
Private _connection as SqlConnection

Protected Sub New (ByVal Connect as String)
_connection = New SqlConnection (Connect)
End Sub

Protected ReadOnly Property Connection () as SqlConnection
Get
Return _connection
End Get
End Property

Public Sub Dispose () Implements IDisposable.Dispose
_connection. Dispose ()
End Sub

End Class
End Namespace

List 1. Simple base class

As you can see in the list, the Dalbase class is MustInherit labeled (Abstract in C #) to ensure that it is used in inheritance relationships. The class then includes an instantiated private SqlConnection object in a public constructor that receives the connection string as a parameter. When the Dispose method from the IDisposable interface ensures that the connection object is already configured, the protected (Protected) connection property allows the derived class to access the connection object.

Even in the simplified example below you can begin to see the usefulness of the abstract base class:

Public Class webdata:inherits Dalbase
Public Sub New ()
MyBase.New (ConfigurationSettings.AppSettings ("ConnectString"))
End Sub

Public Function getorders () as DataSet
Dim da as New SqlDataAdapter ("Usp_getorders", me.connection)
Da.SelectCommand.CommandType = CommandType.StoredProcedure
Dim DS as New DataSet ()

Da. Fill (DS)
Return DS
End Function
End Class

In this case, the WebData class inherits from the Dalbase, and the result is that you don't have to worry about instantiating the SqlConnection object, but simply pass the connection string to the base class with the MyBase keyword (or the base keyword in C #). The GetOrders method of the WebData class can use Me.connection (this in C #. Connection) access to protected properties. While this example is relatively straightforward, you will see in rules 2 and 3 that the base class also provides other services.

The abstract base class is useful when the data access layer must run in the COM + environment. In this case, because the necessary code to allow the component to use COM + is much more complex, a better way is to create a service component (serviced component) base class, as shown in Listing 2.

Transaction (transactionoption.supported), _
EventTrackingEnabled (True) > _
Public MustInherit Class Dalservicedbase:inherits ServicedComponent

Private _connection as SqlConnection

Protected Overrides Sub Construct (ByVal s as String)
_connection = New SqlConnection (s)
End Sub

Protected ReadOnly Property Connection () as SqlConnection
Get
Return _connection
End Get
End Property
End Class

List 2. Service component base class

In this code, the Dalservicedbase class contains the same basic functionality as in Listing 1, However, it adds the inheritance from the ServicedComponent of the System.EnterpriseServices namespace, and includes attributes that indicate that the component supports object construction, transactions, and static tracing. The base class then carefully captures the constructed strings in the Component Services Manager (Component Services Manager) and establishes and exposes SqlConnection objects again. We should note that when a class inherits from Dalservicedbase, it also inherits the property's settings. In other words, the transaction options for a derived class are also set to supported. If a derived class wants to overload this behavior, it can redefine the property at the class level.

In addition, derived classes should, in appropriate cases, be advantageous to their own overloading and sharing methods. The use of overloaded methods (one method has multiple call signals) is inherently in two cases. First, they are used when a method needs to accept multiple types of arguments. A typical example in a framework component is the System.Convert class method. For example, the ToString method contains 18 overloaded methods that accept one parameter, and each overloaded method is of a different type. Second, overloaded methods are used to expose signals that are growing in number of parameters, rather than the necessary parameters for different types. This type of overload becomes highly efficient in the data access layer because it can be used to retrieve and modify the signals that are alternately exposed for the data. For example, the GetOrders method can overload, such a signal does not accept parameters and return all orders, but the attached signal accepts parameters to indicate that the caller wants to retrieve a specific customer order, the code is as follows:

Public Overloads Function getorders () as DataSet
Public Overloads Function getorders (ByVal customerId as Integer) as DataSet

A good implementation technique in this case is to abstract the functionality of the GetOrders method into a private or protected method that can be invoked by each overloaded signal.

Shared methods (static methods in C #) can also be used to expose fields, properties, and methods that all instances of a data access class can access. Although shared members cannot be used with classes that use Component Services (Component service), it is useful for read-only data that is retrieved and read by all instances in the shared constructor of a data access class. Be careful when you use shared members to read/write data, because multiple threads that are executing may compete to access the shared data.

Rule 2: Stick to design guidelines

With Visual Studio. NET, published in the online documentation, is a topic called the Design Guide for Class library developers (designed guidelines for Class library developers), which overrides the name conversion of classes, properties, and methods, is an overloaded member, A supplemental pattern of constructors and events. One of the main reasons you have to follow name conversions is. NET Framework components to provide cross language (cross-language) inheritance. If you are in visual Basic. NET to create a data access layer base class that you want to make sure to use. NET Framework component compatible with other languages, developers can inherit it and easily understand how it works. By sticking to the guidelines I outlined, your name conversion and construction will not be language-specific (language specific). For example, you may notice that the first word in the code in this example is lowercase, and that intercaps is used for the parameters of the method, and that each word capitalization is used for the method, and the base class uses the base flag to identify it as an abstract class.

Can speculate. NET Framework component design Guidelines are common design patterns, as documented by the designs patterns written by Gang of Four (Addison-wesley, 1995). For example. NET Framework component uses a variant of the observer pattern, called the event pattern, that you must follow when exposing an event in a class.

Rule 3: Leveraging infrastructure (infrastructure)

. NET Framework components include classes and constructs that assist in the handling of common infrastructure-related transactions, such as appliances and exception handling. Combining these concepts with inheritance through the base class will be very powerful. For example, you can consider the trace features that are exposed in the System.Diagnostics name space. In addition to providing the trace and debug classes, the namespace includes classes derived from switch and TraceListener. The BooleanSwitch and TraceSwitch of the switch class can be configured to open and close applications and configuration files, and multiple levels of tracing can be exposed in TraceSwitch. The TextWriterTraceListener and EventLogTraceListener of the TraceListener class navigate the input of the trace and debug methods to the text file and the event log, respectively.

The result of this is that you have added tracing to the base class, making it simpler for the derived class to record the message log. The application can then use the configuration file to control whether tracing is allowed. You can include a BooleanSwitch type of private variable and instantiate it in the constructor to add this functionality to the dalbase in Listing 1:

Public Sub New (ByVal Connect as String)
_connection = New SqlConnection (Connect)
_dalswitch = New BooleanSwitch ("DAL", "Data Access Code")
End Sub

Parameters passed to BooleanSwitch include the name and description. Then you can add a protected property to open and close the switch, or you can add a property to format and write trace messages using the WriteLineIf method of the Trace object:

Protected Property tracingenabled () as Boolean
Get
Return _dalswitch.enabled
End Get
Set (ByVal Value as Boolean)
_dalswitch.enabled = Value
End Set
End Property

Protected Sub writetrace (ByVal message as String)
Trace.WriteLineIf (me.tracingenabled, Now & ":" & Message)
End Sub

In this way, derived classes do not know the switch and listener (listener) classes, and can simply invoke the Writetrace method when the data access class produces a meaningful signal.

Type= "System.Diagnostics.TextWriterTraceListener"
Initializedata= "DALLog.txt"/>

List 3. Tracked configuration Files

To create a listener and open it, you need to use the application configuration file. Listing 3 shows a simple configuration file that opens the data access class switch just shown and textwritertracelistener the output to the file DALLog.txt by MyListener call. Of course, you can build the listener programmatically from the TraceListener class and include the listener directly in the data access class.

Public Class dalexception:inherits ApplicationException
Public Sub New ()
MyBase.New ()
End Sub

Public Sub New (ByVal message as String)
MyBase.New (Message)
End Sub

Public Sub New (ByVal message as String, ByVal innerexception as
Exception)
MyBase.New (message, innerexception)
End Sub
' Add custom Members here
Public ConnectString as String
End Class

List 4. Custom Exception class

The second infrastructure you derive from the proceeds is structured exception handling (SEH). At the most basic level, a data access class can expose its derived from System.ApplicationException exception (exception) objects and further expose custom members. For example, the Dalexception object shown in Listing 4 can be used to wrap the exception generated by code in the data access class. The base class can then expose a protected method to wrap the exception, assemble the custom member, and send it back to the calling program as follows:

Protected Sub throwdalexception (ByVal message as String, _
ByVal innerexception as Exception)
Dim Newmine as New dalexception (message, innerexception)

newmine.connectstring = Me.Connection.ConnectionString
Me.writetrace (Message & "{" & Innerexception.message & "}")
Throw Newmine
End Sub

Using this method, a derived class can simply invoke a protected method, passing in a particular data exception (typically with SqlException or oledbexception), which is intercepted and adds a message that is subordinate to a particular data field. The base class wraps the exception in Dalexception and sends it back to the calling program. This allows the calling program to easily catch all exceptions from the data access class with a catch statement.

As a choice, you can look at the "Exception Management Application Block Overview" released on MSDN. The framework component combines exception and application logging through a series of objects. In fact, custom exception classes derived from the BaseApplicationException class provided by the. NET Framework component can simply insert the framework component.

Rule 4: Carefully select the external interface

When you design methods for data access classes, you need to consider how they accept and return data. For most developers, there are three main choices: using Ado.net objects directly, using XML, and using custom classes.

If you expose Ado.net objects directly, you can use one or two programming models. The first includes datasets and data table objects that are useful for not connecting to data access. There are a lot of articles about datasets and the data tables associated with it, but it is most useful when you have to use data from the lower level to store disconnected data. In other words, datasets can be passed between tiers of the application, even if those layers are physically distributed, and can be used when the business and data Service tiers are placed on the same cluster of servers and separated from the performance services. In addition, dataset objects are an ideal way to return data through xml-based Web services because they are serializable and can therefore be returned in a SOAP response message.

This differs from accessing data using classes that implement the IDataReader interface, such as SqlDataReader and OleDbDataReader. Data reader accesses data in a forward-only, read-only manner. The biggest difference is that datasets and data table objects can be passed between application domains by passing values (by value), whereas data readers can be passed around, but generally by reference (by reference). In Listing 5, read and getvalues are executed during the server process and their return values are copied to the client.

The figure shows how the data reader survives in the application domain, where it is built, and all of its access results are in the loop between the client and server application domains. This means that when the data access method is running in the same application domain, the data reader should be returned as the caller.

There are two issues to consider when using a data reader. First, when you return a data reader from a method of a data access class, you must consider the lifetime of the connection object associated with the data reader. The default is that the connection is still busy when the caller repeats through the data reader, unfortunately when the calling program finishes, the connection is still open, so it does not return to the connection pool (if the connection pool is allowed). However, when the connected Close method is invoked by passing the CommandBehavior.CloseConnection enumeration to the ExecuteReader method of the Command object, you can command the data reader to turn off its connection.

Second, to isolate the presentation layer from a particular framework component data provider (such as SqlClient or OLE DB), the calling code should refer to the return value using the IDataReader interface (for example, SqlDataReader) rather than the specific type. In this way, if the application backend is ported from Oracle to SQL Server, or if the return type of one method of the data access class changes, the presentation layer does not need to be changed.

If you want the data access class to return XML, you can select one from the XmlDocument and XmlReader in the System.Xml namespace, which is similar to the dataset and IDataReader. In other words, your method should return a XmlDocument (or XmlDataDocument) when the data is disconnected from the data source, but XmlReader can be used to access the stream of XML data.

Finally, you can also decide to return the custom class with the public property. These classes can use serialization (serialized) attribute tags so that they can be replicated across application domains. In addition, if you return multiple objects from a method, you need a collection class that reinforces the type (strongly typed).

Imports System.Xml.Serialization

_
Public Class book:implements IComparable
Public ProductID as Integer
Public ISBN as String
Public Title as String
Public Author as String
Public UnitCost as Decimal
Public Description as String
Public pubdate as Date

Public Function CompareTo (ByVal o as Object) as Integer _
Implements IComparable.CompareTo
Dim B as book = CType (o, book)
Return Me.Title.CompareTo (B.title)
End Function
End Class

Public NotInheritable Class Bookcollection:inherits ArrayList
Default Public Shadows Property Item (ByVal productId as Integer) _
As book
Get
Return Me (IndexOf (productId))
End Get
Set (ByVal Value as book)
Me (IndexOf (productId)) = Value
End Set
End Property

Public Overloads Function Contains (ByVal productId as Integer) as _
Boolean
Return ( -1 <> IndexOf (productId))
End Function

Public Overloads Function IndexOf (ByVal productId as Integer) as _
Integer
Dim Index as Integer = 0
Dim Item as Book

For each item in Me
IF item. ProductID = ProductID Then
return index
End If
index = index + 1
Next
Return-1
End Function

Public Overloads Sub RemoveAt (ByVal productId as Integer)
RemoveAt (IndexOf (productId))
End Sub

Public Shadows Function ADD (ByVal value as book) as Integer
return Mybase.add (value)
End Function
End Class

List 6. Using a custom class

The list above (listing 6) contains an example of a simple book class and the collection class associated with it. You can notice that the book class is marked with serializable so that it can use the "by value" syntax across the application domain. This class implements the IComparable interface, so when it is contained in a collection class, it is sorted by title by default. The Bookcollection class derives from the ArrayList of the System.Collections namespace and hides the Item property and the Add method in order to restrict the collection to the book object.

By using a custom class you completely control the performance of the data, the developer's efficiency, and no reliance on ado.net calls. But this approach requires more code, because. NET Framework component does not contain any technology mappings associated with the object. In this case, you should create a data reader in the data access class and use it to combine the custom class.

Rule 5: Abstraction. NET Framework component Data Provider

The last rule explains why and how abstract data access classes are used internally. NET Framework component data provider (provider). I said earlier that the Ado.net programming model exposes specific. NET Framework component data providers, including those available on SqlClient, OLE DB, and other MSDN Online Web sites. But the result of this design is the ability to improve performance, exposing the functionality of a specific data source to a data provider, forcing you to decide which data provider to use that code. In other words, developers typically choose to use SqlClient or OLE DB and then program their classes directly in their namespaces.

If you want to change. NET Framework component data provider, you must rewrite the data access method. To avoid this happening, you can use the abstract factory design pattern. Using this pattern, you can create a simple class that exposes methods to build the main. NET Framework component Data Provider Objects (command, connection, data adapter, and parameter), and those objects are based on those that are passed to the constructor. NET Framework component data provider information. The code in Listing 7 is such a simple class.

public enum Providertype:int {SqlClient = 0, OLE DB = 1}

public class ProviderFactory {
Public ProviderFactory (ProviderType provider) {
_ptype = provider;
_initclass ();
}

Public ProviderFactory () {
_initclass ();
}

Private ProviderType _ptype = providertype.sqlclient;
private bool _ptypeset = false;
Private type[] _contype, _comtype, _parmtype, _datype;


private void _initclass () {
_contype = new Type[2];
_comtype = new Type[2];
_parmtype = new Type[2];
_datype = new Type[2];

Initialize type for provider
_contype[(int) providertype.sqlclient] = typeof (SqlConnection);
_contype[(int) providertype.oledb] = typeof (OleDbConnection);
_comtype[(int) providertype.sqlclient] = typeof (SqlCommand);
_comtype[(int) providertype.oledb] = typeof (OleDbCommand);
_parmtype[(int) providertype.sqlclient] = typeof (SqlParameter);
_parmtype[(int) providertype.oledb] = typeof (OleDbParameter);
_datype[(int) providertype.sqlclient] = typeof (SqlDataAdapter);
_datype[(int) providertype.oledb] = typeof (OleDbDataAdapter);
}

Public ProviderType Provider {
get {
return _ptype;
}
set {
if (_ptypeset) {
throw new Readonlyexception ("Provider already set to"
+ _ptype.tostring ());
}
else {
_ptype = value;
_ptypeset = true;
}
}
}
Public IDataAdapter Createdataadapter (string commandtext,idbconnection
Connection) {
IDataAdapter D;
IDbDataAdapter da;

D = (idataadapter) activator.createinstance (_datype[(int) _ptype],
FALSE);
da = (idbdataadapter) D;
Da. SelectCommand = this. CreateCommand (CommandText, connection);
return D; }

Public Idataparameter CreateParameter (string paramname, DbType
Paramtype) {
Idataparameter p;
p = (idataparameter) activator.createinstance (_parmtype[(int) _ptype],
FALSE);
P.parametername = paramname;
P.dbtype = Paramtype;
return p;
}

Public Idataparameter CreateParameter (string paramname, DbType
Paramtype, Object value) {
Idataparameter p;
p = (idataparameter) activator.createinstance (_parmtype[(int) _ptype],
FALSE);
P.parametername = paramname;
P.dbtype = Paramtype;
P.value = Value;
return p;
}

Public IDbConnection createconnection (string connect) {
IDbConnection C;
c = (idbconnection) activator.createinstance (_contype[(int) _ptype],
FALSE);
c.connectionstring = connect;
return C;
}

Public IDbCommand CreateCommand (string cmdtext, IDbConnection
Connection) {
IDbCommand C;
c = (IDbCommand) activator.createinstance (_comtype[(int) _ptype],
FALSE);
C.commandtext = Cmdtext;
C.connection = Connection;
return C;
}
}

Listing 7. ProviderFactory

In order to use this class, the code for the data access class must be multiple. NET Framework component data provider implements interfaces (including IDbCommand, IDbConnection, IDataAdapter, and Idataparameter) for programming. For example, in order to populate a DataSet with the return value of a parameterized stored procedure, you must have the following code in a method of the data access class:

Dim _PF as New providerfactory (providertype.sqlclient)
Dim cn as IDbConnection = _PF. CreateConnection (_connect)
Dim da as IDataAdapter = _pf. Createdataadapter ("Usp_getbook", CN)

Dim db as IDbDataAdapter = CType (DA, IDbDataAdapter)
Db.SelectCommand.CommandType = CommandType.StoredProcedure
Db. SELECTCOMMAND.PARAMETERS.ADD (_PF. CreateParameter ("@productId", Dbtype.int32, id))

Dim DS as New DataSet ("books")
Da. Fill (DS)

Typically, you declare a providerfactory variable at the class level and instantiate it in the constructor of the data access class. In addition, its constructors are assembled with providers that are read from the configuration file, not hard code. As you can imagine, ProviderFactory is a significant addition to the data access class and can be included in the widget and distributed to other developers.

Conclusion

In the age of Web services, more and more applications will be built to operate data from separate application tiers. If you follow some basic rules and form a habit, writing data access code will be quicker, easier, and more reusable, saving your errors to the server and allowing you to keep your data independent.



Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.