抽象工厂在ADO.Net中的应用
https://msdn.microsoft.com/zh-cn/library/ms971499.aspx
http://www.c-sharpcorner.com/UploadFile/mosessaur/abstractfactoryadonet202152006053643AM/abstractfactoryadonet2.aspx
Introduction
Most Web applications contain data access code to access the underlying data store to perform basic data operations such as Select, Update, Delete, and Insert. This article uses a step-by-step approach to show how page developers can take advantage of different ASP.NET 2.0 and ADO.NET 2.0 tools and techniques to write generic data access code that can be used to access different types of data stores. Writing generic data access code is especially important in data-driven Web applications because data comes from many different sources, including Microsoft SQL Server, Oracle, XML documents, flat files, and Web services, just to name a few.
This article uses a simple Web application as a test bed for all the code presented here. The application consists of two parts: the first part allows the system administrator to send newsletters to all subscribers of a mailing list. The second part allows users to subscribe or unsubscribe from a mailing list. The first part of the article begins by implementing a simple data access code (see Figure 1) to access Microsoft SQL Server and extract the list of subscribers. The code is modified and made more generic over the course of the article.
Figure 1. The GetSubscribers method extracts the list of all subscribers.
public IEnumerable GetSubscribers() { SqlConnection con = new SqlConnection(); con.ConnectionString = @"Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf"; SqlCommand com = new SqlCommand(); com.Connection = con; com.CommandText = "Select * From Subscribers"; com.CommandType = CommandType.Text; DataSet ds = new DataSet(); SqlDataAdapter ad = new SqlDataAdapter(); ad.SelectCommand = com; con.Open(); ad.Fill(ds); con.Close(); return ds.Tables[0].DefaultView; }
Since the data access code in Figure 1 contains code for creating the ADO.NET objects, such as the SqlConnection, SqlCommand, and SqlDataAdapter instances. The data access code cannot be used to retrieve the list of subscribers from other data stores, such as an Oracle database. Page developers have to modify the data access code (using the GetSubscribers method) every time they need to access a new data store. Next, see how ADO.NET 2.0 uses the provider pattern to help page developers write generic data access code to access different types of data stores.
Provider Pattern in ADO.NET 2.0
The main problem with the GetSubscribers method is that it contains the code for creating the ADO.NET objects. According to the provider pattern, the data access code must delegate the responsibility of providing the code for creating the ADO.NET objects to another class. I refer to this class as the "code provider class" because it provides the code for creating the ADO.NET objects. The code provider class exposes methods such as CreateConnection, CreateCommand, and CreateDataAdapter, where each method provides the code for creating the corresponding ADO.NET object.
Since the code provider class contains the actual code, the same class cannot be used to access different data stores. Therefore, the data access code (the GetSubscribers method) has to be modified and reconfigured to delegate the responsibility of providing the code to a new code provider class each time it is used to access a new data store. The GetSubscribers method is still tied to the code even though it does not contain the code.
The provider pattern offers a solution to this problem and consists of the following steps:
- Design and implement an abstract base provider class.
- Derive all code provider classes from the abstract base provider class.
- Have the data access code (the GetSubscribers method) to use the abstract base class instead of the individual code provider classes.
The abstract base class delegates the responsibility of providing the code for creating the ADO.NET objects to the appropriate subclass. The abstract base class is named DbProviderFactory. The following presents some of the methods of this class:
public abstract class DbProviderFactory { public virtual DbConnection CreateConnection(); public virtual DbCommand CreateCommand(); public virtual DbDataAdapter CreateDataAdapter(); }
Each subclass provides the code for creating the appropriate ADO.NET objects for a particular data store. For instance, the SqlClientFactory subclass provides the code for creating the ADO.NET objects to access Microsoft SQL Server, as shown in Figure 2.
Figure 2. The SqlClientFactory class and some of its methods
The provider pattern allows the data access code to treat all the subclasses the same because they are all subclasses of the same base class. As far as the data access code is concerned, all subclasses are of type DbProviderFactory. The data access code has no way of knowing the specific type of the subclass being used. This introduces a new problem. If the data access code (the GetSubscribers method) does not know the type of subclass, how can it then instantiate an instance of the subclass?
The provider pattern solution to this problem consists of the following three parts:
- A unique string is used to identify each subclass. ADO.NET 2.0 uses the namespace of the subclass as its unique string id. For instance, the unique string id‘s System.Data.SqlClient andSystem.Data.OracleClient identify SqlClientFactory and OracleClientFactory subclasses, respectively.
- A text file (normally an XML file) is used to store information about all the subclasses. ADO.NET 2.0 uses the machine.config and web.config files to store the required information. The information about a subclass contains, among other things, the unique string id and the name of the type of the subclass. For instance, the information about the SqlClientFactory subclass includes the unique string idSystem.Data.SqlClient and the name of the type of the subclass, i.e., System.Data.SqlClient.SqlClientFactory.
- A static method is designed and implemented. The method could be part of the abstract base class or part of a separate class. ADO.NET 2.0 uses a separate class named DbProviderFactories that exposes theGetFactory static method. The method takes the unique string id of the desired subclass as its only argument and searches through the machine.config file for a subclass with the given unique string id. The method extracts the name of the type of the desired subclass and uses reflection to dynamically create an instance of the subclass.
Data access code (the GetSubscribers method) calls the GetFactory static method and passes the appropriate unique string id to access the instance of the corresponding subclass. After the GetSubscribers method accesses the instance, it calls the appropriate creation methods, such as CreateConnection(), CreateCommand(), etc., to instantiate the appropriate ADO.NET objects, as shown in Figure 3.
Figure 3. The version of the GetSubscribers method that uses the new ADO.NET provider pattern
public IEnumerable GetSubscribers() { DbProviderFactory provider = DbProviderFactories.GetFactory("System.Data.SqlClient"); DbConnection con = provider.CreateConnection(); con.ConnectionString = @"Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf"; DbCommand com = provider.CreateCommand(); com.Connection = con; com.CommandText = "Select * From Subscribers"; com.CommandType = CommandType.Text; DataSet ds = new DataSet(); DbDataAdapter ad = provider.CreateDataAdapter(); ad.SelectCommand = com; con.Open(); ad.Fill(ds); con.Close(); return ds.Tables[0].DefaultView; }
The data access code (the GetSubscribers method) delegates the responsibility of providing the code for creating the ADO.NET objects to the code provider class instance that the GetFactory method instantiates and returns. Therefore, the same data access code can be used to access different data stores, such as Microsoft SQL Server and Oracle.
The code for creating the right ADO.NET objects is data-store–specific. The provider pattern in ADO.NET 2.0 removes these data-store–specific parts from the data access code (the GetSubscribers method) to make it more generic. However, the provider pattern does not remove all the data-store–specific parts. Closer inspection of the GetSubscribers method reveals the following remaining data-store–specific parts:
- Connection string
- Unique string id that identifies the underlying code provider class
- Command text
- Command type
Unless something is done about the above parts, the data access code is still tied to a particular type of data store. The provider pattern in ADO.NET 2.0 does not help with this problem. However, ADO.NET 2.0 provides us with other tools and techniques to remove the first two data-store–specific parts, such as the connection string and unique string id from the GetSubscribers method.
Connection Strings
Connection strings are some of the most valuable resources in a Web application. They are so important that the .NET Framework 2.0 treats them as "first-class citizens". The web.config file now supports a new section named <connectionStrings> that contains all the connection strings used in an application. Therefore, we will move the connection string from the GetSubscribers method to this section:
<?xml version="1.0" encoding="utf-8" ?> <configuration> <connectionStrings> <add name="MySqlConnectionString" connectionString="Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf" providerName="System.Data.SqlClient"/> </connectionStrings> </configuration>
The <add> subelement of the <connectionStrings> element exposes the following three important attributes:
- Name—The friendly name of the connection string
- connectionString—The actual connection string
- providerName—The unique string id or invariant of the code provider class
NET Framework 2.0 provides the data access code (the GetSubscribers method) with the right tools to generically extract the connection string value from the web.config file as described in the following. TheSystem.Configuration namespace in .NET Framework 2.0 includes a new class named Configuration. This class represents the entire content of a web.config or machine.config file. The data access code cannot use the new operator to directly create an instance of this class.
Figure 4 shows the new version of the GetSubscribers method that contains the required code to extract the connection string in generic fashion.
Figure 4. The version of the GetSubscribers method that extracts the connection string from the web.config file
public IEnumerable GetSubscribers()
{
DbProviderFactory provider = DbProviderFactories.GetFactory("System.Data.SqlClient");
DbConnection con = provider.CreateConnection();
Configuration configuration = Configuration.GetWebConfiguration("~/");
ConnectionStringsSection section = (ConnectionStringsSection)configuration.Sections["connectionStrings"];
con.ConnectionString = section.ConnectionStrings["MySqlConnectionString"].ConnectionString;
DbCommand com = provider.CreateCommand();
com.Connection = con;
com.CommandText = "Select * From Subscribers";
com.CommandType = CommandType.Text;
DataSet ds = new DataSet();
DbDataAdapter ad = provider.CreateDataAdapter();
ad.SelectCommand = com;
con.Open();
ad.Fill(ds);
con.Close();
return ds.Tables[0].DefaultView;
}
The GetSubscribers method now includes the MySqlConnectionString string, so we still have to modify the GetSubscribers method to add support for a different data store such as Oracle database. It would seem that we are back to square one. Not really. We have gained a few important benefits by moving the connection string from the data access code to the web.config file:
- The connection string is now the value of the connectionString attribute of the <add> sub element of the connectionStrings element of the web.config file, which is an XML document. The great thing about an XML document is that we can encrypt a single element in the document. We do not have to encrypt the entire document if we only need to protect small part of it. The .NET Framework 2.0 comes with a tool that allows us to encrypt the <connectionStrings> section to protect our most important resource, the connection strings. Imagine how much damage a hacker can do to our valuable database if he gets his hand on our connection strings. Remember connection strings are all a hacker needs to access our database.
- It may seem that all we have done is to replace the following string
"Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf"
with the new string, MySqlConnectionString. However, there is one big difference. The former string contains the SQL Server database-specific information that does not apply to another database such as Oracle, but the latter string is just a friendly name.
However, the friendly name could still cause problems because it refers to a specific connection string within the <connectionStrings> section of the web.config file. In our example, it refers to the connection string used to access Microsoft SQL Server. This means that the GetSubscribers method (the data access code) has to be modified to use a different friendly name to access a different data store such as Oracle.
To avoid modifying the data access code, we can move the friendly name from the data access code to the <appSettings> section of the web.config file and have the data access code dynamically extract it in runtime as follows:
string connectionStringName = ConfigurationSettings.AppSettings["ConnectionStringName"];
We also move the provider name to the <appSettings> section:
string providerName = ConfigurationSettings.AppSettings["ProviderName"];
Page developers simply change the value attribute of the <add> subelement of the <appSettings> element to the same data access code to access a different data store without making any changes in the data access code itself.
Figure 5 presents the version of the data access code (the GetSubscribers method) that contains the recent changes.
Figure 5. The version of the GetSubscribers method to extract the provider name and the friendly name of the connection string from the web.config file
public IEnumerable GetSubscribers() { string connectionStringName = ConfigurationSettings.AppSettings["ConnectionStringName"]; string providerName = ConfigurationSettings.AppSettings["ProviderName"]; Configuration configuration = Configuration.GetWebConfiguration("~/"); ConnectionStringsSection section = (ConnectionStringsSection)configuration.Sections["connectionStrings"]; DbProviderFactory provider = DbProviderFactories.GetFactory(providerName); DbConnection con = provider.CreateConnection(); con.ConnectionString = section.ConnectionStrings[connectionStringName].ConnectionString; DbCommand com = provider.CreateCommand(); com.Connection = con; com.CommandText = "Select * From Subscribers"; com.CommandType = CommandType.Text; DataSet ds = new DataSet(); DbDataAdapter ad = provider.CreateDataAdapter(); ad.SelectCommand = com; con.Open(); ad.Fill(ds); con.Close(); return ds.Tables[0].DefaultView; }
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。