Schreiben von generischem Datenzugriffscode in ASP.NET 2.0 und ADO.NET 2.0
Dr. Shahram Khosravi
Informationslösungen
April 2005
Gilt für:
Microsoft ADO.NET 2.0
Microsoft ASP.NET 2.0
Microsoft .NET Framework 2.0
Microsoft Visual Web Developer 2005 Express Edition Beta
C#-Programmiersprache
Zusammenfassung: Verwenden Sie einen schrittweisen Ansatz, um zu erfahren, wie Verschiedene ASP.NET 2.0- und ADO.NET 2.0-Tools und -Techniken zum Schreiben von generischem Datenzugriffscode verwendet werden. (18 gedruckte Seiten)
Laden Sie den zugehörigen Beispielcode herunter: GenericDataAccessSample.exe.
Einleitung
Die meisten Webanwendungen enthalten Datenzugriffscode, um auf den zugrunde liegenden Datenspeicher zuzugreifen, um grundlegende Datenvorgänge auszuführen, z. B. Select, Update, Deleteund Insert. In diesem Artikel wird ein schrittweiser Ansatz verwendet, um zu zeigen, wie Seitenentwickler verschiedene ASP.NET 2.0 und ADO.NET 2.0-Tools und -Techniken nutzen können, um generischen Datenzugriffscode zu schreiben, der für den Zugriff auf verschiedene Datentypen von Datenspeichern verwendet werden kann. Das Schreiben von generischem Datenzugriffscode ist in datengesteuerten Webanwendungen besonders wichtig, da Daten aus vielen verschiedenen Quellen stammen, darunter Microsoft SQL Server, Oracle, XML-Dokumente, Flat files und Webdienste, um nur einige zu nennen.
In diesem Artikel wird eine einfache Webanwendung als Testbett für den gesamten hier vorgestellten Code verwendet. Die Anwendung besteht aus zwei Teilen: Der erste Teil ermöglicht es dem Systemadministrator, Newsletter an alle Abonnenten einer Mailingliste zu senden. Im zweiten Teil können Benutzer eine Adressenliste abonnieren oder kündigen. Der erste Teil des Artikels beginnt mit der Implementierung eines einfachen Datenzugriffscodes (siehe Abbildung 1), um auf Microsoft SQL Server zuzugreifen und die Liste der Abonnenten zu extrahieren. Der Code wird im Laufe des Artikels generisch geändert und generisch gestaltet.
Abbildung 1. Die GetSubscribers-Methode extrahiert die Liste aller Abonnenten.
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;
}
Da der Datenzugriffscode in Abbildung 1 Code zum Erstellen der ADO.NET Objekte enthält, z. B. die SqlConnection-, SqlCommand-und SqlDataAdapter- Instanzen. Der Datenzugriffscode kann nicht verwendet werden, um die Liste der Abonnenten aus anderen Datenspeichern abzurufen, z. B. eine Oracle-Datenbank. Seitenentwickler müssen den Datenzugriffscode (mithilfe der GetSubscribers Methode) jedes Mal ändern, wenn sie auf einen neuen Datenspeicher zugreifen müssen. Als Nächstes erfahren Sie, wie ADO.NET 2.0 das Anbietermuster verwendet, um Seitenentwicklern beim Schreiben von generischem Datenzugriffscode zu helfen, um auf verschiedene Datentypen von Datenspeichern zuzugreifen.
Anbietermuster in ADO.NET 2.0
Das Hauptproblem bei der GetSubscribers-Methode besteht darin, dass sie den Code zum Erstellen der ADO.NET-Objekte enthält. Gemäß dem Anbietermuster muss der Datenzugriffscode die Verantwortung für die Bereitstellung des Codes zum Erstellen der ADO.NET Objekte an eine andere Klasse delegieren. Ich beziehe mich auf diese Klasse als "Codeanbieterklasse", da sie den Code zum Erstellen der ADO.NET-Objekte bereitstellt. Die Codeanbieterklasse macht Methoden wie CreateConnection, CreateCommandund CreateDataAdapterverfügbar, wobei jede Methode den Code zum Erstellen des entsprechenden ADO.NET-Objekts bereitstellt.
Da die Codeanbieterklasse den tatsächlichen Code enthält, kann dieselbe Klasse nicht für den Zugriff auf verschiedene Datenspeicher verwendet werden. Daher muss der Datenzugriffscode (die GetSubscribers-Methode) geändert und neu konfiguriert werden, um die Verantwortung zu delegieren, den Code bei jeder Verwendung für den Zugriff auf einen neuen Datenspeicher an eine neue Codeanbieterklasse bereitzustellen. Die GetSubscribers-Methode ist weiterhin an den Code gebunden, obwohl sie den Code nicht enthält.
Das Anbietermuster bietet eine Lösung für dieses Problem und besteht aus den folgenden Schritten:
Entwerfen und Implementieren einer abstrakten Basisanbieterklasse.
Leiten Sie alle Codeanbieterklassen von der abstrakten Basisanbieterklasse ab.
Verwenden Sie den Datenzugriffscode (die GetSubscribers-Methode), um die abstrakte Basisklasse anstelle der einzelnen Codeanbieterklassen zu verwenden.
Die abstrakte Basisklasse delegiert die Verantwortung für die Bereitstellung des Codes zum Erstellen der ADO.NET Objekte an die entsprechende Unterklasse. Die abstrakte Basisklasse wird DbProviderFactory-benannt. Im Folgenden sind einige der Methoden dieser Klasse aufgeführt:
public abstract class DbProviderFactory { public virtual DbConnection CreateConnection(); public virtual DbCommand CreateCommand(); public virtual DbDataAdapter CreateDataAdapter(); }
Jede Unterklasse stellt den Code zum Erstellen der entsprechenden ADO.NET Objekte für einen bestimmten Datenspeicher bereit. Beispielsweise stellt die SqlClientFactory-Unterklasse den Code zum Erstellen der ADO.NET Objekte für den Zugriff auf Microsoft SQL Server bereit, wie in Abbildung 2 dargestellt.
Abbildung 2. Die SqlClientFactory-Klasse und einige seiner Methoden
public class SqlClientFactory : DbProviderFactory { public override DbConnection CreateConnection() { return new SqlConnection(); } public override DbCommand CreateCommand() { return new SqlCommand(); } public override DbDataAdapter CreateDataAdapter() { return new SqlDataAdapter(); } }
Das Anbietermuster ermöglicht es dem Datenzugriffscode, alle Unterklassen gleich zu behandeln, da sie alle Unterklassen derselben Basisklasse sind. Was den Datenzugriffscode betrifft, sind alle Unterklassen vom Typ DbProviderFactory. Der Datenzugriffscode hat keine Möglichkeit, den spezifischen Typ der verwendeten Unterklasse zu kennen. Dies führt zu einem neuen Problem. Wenn der Datenzugriffscode (die GetSubscribers-Methode) den Typ der Unterklasse nicht kennt, wie kann er dann eine Instanz der Unterklasse instanziieren?
Die Lösung des Anbietermusters für dieses Problem besteht aus den folgenden drei Teilen:
- Eine eindeutige Zeichenfolge wird verwendet, um jede Unterklasse zu identifizieren. ADO.NET 2.0 verwendet den Namespace der Unterklasse als eindeutige Zeichenfolgen-ID. Beispielsweise System.Data.SqlClient der eindeutigen Zeichenfolgen-ID und System.Data.OracleClientSqlClientFactory bzw. OracleClientFactory Unterklassen identifizieren.
- Eine Textdatei (normalerweise eine XML-Datei) wird verwendet, um Informationen zu allen Unterklassen zu speichern. ADO.NET 2.0 verwendet die dateien machine.config und web.config zum Speichern der erforderlichen Informationen. Die Informationen zu einer Unterklasse enthalten u. a. die eindeutige Zeichenfolgen-ID und den Namen des Typs der Unterklasse. Beispielsweise enthalten die Informationen zur SqlClientFactory-Unterklasse die eindeutige Zeichenfolgen-ID System.Data.SqlClient- und den Namen des Typs der Unterklasse, d. h. System.Data.SqlClientFactory.
- Eine statische Methode wurde entworfen und implementiert. Die Methode kann Teil der abstrakten Basisklasse oder Teil einer separaten Klasse sein. ADO.NET 2.0 verwendet eine separate Klasse namens DbProviderFactories, die die GetFactory- statische Methode verfügbar macht. Die Methode verwendet die eindeutige Zeichenfolgen-ID der gewünschten Unterklasse als einziges Argument und durchsucht die datei machine.config nach einer Unterklasse mit der angegebenen eindeutigen Zeichenfolgen-ID. Die Methode extrahiert den Namen des Typs der gewünschten Unterklasse und verwendet Spiegelung, um eine Instanz der Unterklasse dynamisch zu erstellen.
Der Datenzugriffscode (die GetSubscribers-Methode) ruft die statische GetFactory-Methode auf und übergibt die entsprechende eindeutige Zeichenfolgen-ID, um auf die Instanz der entsprechenden Unterklasse zuzugreifen. Nachdem die GetSubscribers-Methode auf die Instanz zugegriffen hat, ruft sie die entsprechenden Erstellungsmethoden wie CreateConnection(), CreateCommand(), usw. auf, um die entsprechenden ADO.NET Objekte zu instanziieren, wie in Abbildung 3 dargestellt.
Abbildung 3. Die Version der GetSubscribers-Methode, die das neue ADO.NET Anbietermuster verwendet
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;
}
Der Datenzugriffscode (die GetSubscribers-Methode) delegiert die Verantwortung für die Bereitstellung des Codes zum Erstellen der ADO.NET Objekte an die Codeanbieterklasseninstanz, die die GetFactory-Methode instanziiert und zurückgibt. Daher kann derselbe Datenzugriffscode verwendet werden, um auf verschiedene Datenspeicher zuzugreifen, z. B. Microsoft SQL Server und Oracle.
Der Code zum Erstellen der richtigen ADO.NET-Objekte ist datenspeicherspezifisch. Das Anbietermuster in ADO.NET 2.0 entfernt diese datenspeicherspezifischen Teile aus dem Datenzugriffscode (die GetSubscribers-Methode), um es allgemeiner zu gestalten. Das Anbietermuster entfernt jedoch nicht alle datenspeicherspezifischen Teile. Genauere Betrachtung der GetSubscribers-Methode zeigt die folgenden verbleibenden Datenspeicher-spezifischen Teile:
- Verbindungszeichenfolge
- Eindeutige Zeichenfolgen-ID, die die zugrunde liegende Codeanbieterklasse identifiziert
- Befehlstext
- Befehlstyp
Sofern nichts über die oben genannten Teile erfolgt, ist der Datenzugriffscode weiterhin an einen bestimmten Datenspeichertyp gebunden. Das Anbietermuster in ADO.NET 2.0 hilft diesem Problem nicht. ADO.NET 2.0 bietet uns jedoch weitere Tools und Techniken zum Entfernen der ersten beiden datenspeicherspezifischen Teile, z. B. die Verbindungszeichenfolge und eindeutige Zeichenfolgen-ID aus der GetSubscribers-Methode.
Verbindungszeichenfolgen
Verbindungszeichenfolgen sind einige der wertvollsten Ressourcen in einer Webanwendung. Sie sind so wichtig, dass .NET Framework 2.0 sie als "Erstklassige Bürger" behandelt. Die web.config-Datei unterstützt jetzt einen neuen Abschnitt mit dem Namen <connectionStrings>, der alle in einer Anwendung verwendeten Verbindungszeichenfolgen enthält. Daher verschieben wir die Verbindungszeichenfolge aus der GetSubscribers-Methode in diesen Abschnitt:
<?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>
Die <fügen> Unterelement des <connectionStrings>-Elements die folgenden drei wichtigen Attribute offen:
- Name– Der Anzeigename der Verbindungszeichenfolge
- connectionString-– Die tatsächliche Verbindungszeichenfolge
- providerName– Die eindeutige Zeichenfolgen-ID oder invariant der Codeanbieterklasse
NET Framework 2.0 stellt den Datenzugriffscode (die GetSubscribers-Methode) mit den richtigen Tools bereit, um den Verbindungszeichenfolgenwert generisch aus der web.config Datei zu extrahieren, wie im Folgenden beschrieben. Der System.Configuration Namespace in .NET Framework 2.0 enthält eine neue Klasse namens Configuration. Diese Klasse stellt den gesamten Inhalt einer web.config oder machine.config Datei dar. Der Datenzugriffscode kann den neuen Operator nicht verwenden, um direkt eine Instanz dieser Klasse zu erstellen.
Die Klasse selbst macht eine statische Methode namens GetWebConfiguration verfügbar, die den Pfad zur web.config Datei verwendet und eine Instanz der Configuration Klasse zurückgibt, die den gesamten Inhalt der web.config Datei darstellt:
Configuration configuration = Configuration.GetWebConfiguration("~/");
Eine Klasse, die von der ConfigurationSection Klasse erbt, stellt jeden Abschnitt der web.config Datei dar. Der Name der Klasse besteht aus dem Namen des Abschnitts und dem Schlüsselwort "Section". Beispielsweise stellt die ConnectionStringsSection Klasse den Inhalt des <> Abschnitts der web.config Datei dar. Der Datenzugriffscode (die GetSubscribers-Methode) kann den neuen Operator nicht verwenden, um direkt eine Instanz der ConnectionStringsSection-Klasse zu erstellen. Die Configuration-Klasse macht eine Auflistungseigenschaft namens Sections verfügbar, die alle Objekte enthält, die unterschiedliche Abschnitte der web.config Datei darstellen:
ConnectionStringsSection section = (ConnectionStringsSection)configuration.Sections["connectionStrings"];
Da Sections eine Auflistung von ConfigurationSection-Objekten ist, muss der Datenzugriffscode die zurückgegebene Instanz umwandeln. Nachdem die GetSubscribers-Methode auf das ConnectionStringsSection-Objekt zugreift und verwendet sie für den Zugriff auf den Verbindungszeichenfolgenwert:
string connectionString = section.ConnectionStrings["MySqlConnectionString"].ConnectionString;
Abbildung 4 zeigt die neue Version der GetSubscribers-Methode, die den erforderlichen Code zum Extrahieren der Verbindungszeichenfolge in generischer Weise enthält.
Abbildung 4. Die Version der GetSubscribers-Methode, die die Verbindungszeichenfolge aus der datei web.config extrahiert.
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;
}
Die GetSubscribers-Methode enthält jetzt die MySqlConnectionString- Zeichenfolge. Daher müssen wir die GetSubscribers-Methode weiterhin ändern, um Unterstützung für einen anderen Datenspeicher wie Oracle-Datenbank hinzuzufügen. Es scheint, dass wir wieder auf platz eins sind. Nicht wirklich. Wir haben einige wichtige Vorteile gewonnen, indem wir die Verbindungszeichenfolge aus dem Datenzugriffscode in die web.config Datei verschieben:
Die Verbindungszeichenfolge ist nun der Wert des connectionString-Attributs des <hinzufügen> Unterelement des connectionStrings-Elements der web.config Datei, bei dem es sich um ein XML-Dokument handelt. Das Große an einem XML-Dokument ist, dass wir ein einzelnes Element im Dokument verschlüsseln können. Wir müssen das gesamte Dokument nicht verschlüsseln, wenn wir nur einen kleinen Teil davon schützen müssen. Das .NET Framework 2.0 enthält ein Tool, mit dem wir die <connectionStrings> Abschnitt verschlüsseln können, um unsere wichtigste Ressource, die Verbindungszeichenfolgen, zu schützen. Stellen Sie sich vor, wie viel Schaden ein Hacker in unserer wertvollen Datenbank tun kann, wenn er seine Hand auf unsere Verbindungszeichenfolgen erhält. Denken Sie daran, dass Verbindungszeichenfolgen alle ein Hacker benötigt, um auf unsere Datenbank zuzugreifen.
Es mag vorkommen, dass wir alle getan haben, die folgende Zeichenfolge zu ersetzen.
"Data Source=.\SQLExpress;Integrated Security=True;AttachDBFilename=D:\Application\Data\Database.mdf"
mit der neuen Zeichenfolge MySqlConnectionString. Es gibt jedoch einen großen Unterschied. Die frühere Zeichenfolge enthält die datenbankspezifischen SQL Server-Informationen, die nicht für eine andere Datenbank wie Oracle gelten, aber die letztere Zeichenfolge ist nur ein Anzeigename.
Der Anzeigename kann jedoch weiterhin Probleme verursachen, da er sich auf eine bestimmte Verbindungszeichenfolge innerhalb des <connectionStrings> Abschnitts der web.config Datei bezieht. In unserem Beispiel bezieht es sich auf die Verbindungszeichenfolge, die für den Zugriff auf Microsoft SQL Server verwendet wird. Dies bedeutet, dass die GetSubscribers-Methode (der Datenzugriffscode) geändert werden muss, um einen anderen Anzeigenamen für den Zugriff auf einen anderen Datenspeicher wie Oracle zu verwenden.
Um das Ändern des Datenzugriffscodes zu vermeiden, können wir den Anzeigenamen aus dem Datenzugriffscode in den abschnitt <appSettings> der web.config Datei verschieben und den Datenzugriffscode dynamisch in Laufzeit extrahieren lassen:
string connectionStringName = ConfigurationSettings.AppSettings["ConnectionStringName"];
Außerdem wird der Anbietername in den Abschnitt <appSettings> verschoben:
string providerName = ConfigurationSettings.AppSettings["ProviderName"];
Seitenentwickler ändern einfach den Wert Attribut des <fügen> Unterelement des <appSettings->-Elements in denselben Datenzugriffscode um, um auf einen anderen Datenspeicher zuzugreifen, ohne Änderungen am Datenzugriffscode selbst vorzunehmen.
Abbildung 5 zeigt die Version des Datenzugriffscodes (die GetSubscribers-Methode), die die letzten Änderungen enthält.
Abbildung 5. Die Version der GetSubscribers-Methode zum Extrahieren des Anbieternamens und des Anzeigenamens der Verbindungszeichenfolge aus der web.config Datei
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;
}
Datenquellensteuerelemente
Die neueste Version der GetSubscribers-Methode, wie in Abbildung 5 dargestellt, ist aufgrund der folgenden Probleme immer noch nicht generisch:
- Die Methode enthält datenspeicherspezifische Informationen, d. h. den Befehlstext und den Befehlstyp. Daher müssen Seitenentwickler die Methode weiterhin ändern, bevor sie sie verwenden können, um auf eine andere Datenbank zuzugreifen.
- Die Methode gibt eine Instanz der DataView Klasse an die Aufrufer zurück, sodass die Methode tabellarische Daten der Aufrufer einspeist. Wir können uns dies als Vertrag zwischen der GetSubscribers-Methode und ihren Aufrufern vorstellen. Die Aufrufer erwarten, dass die GetSubscribers-Methode den Vertrag unter allen Umständen berücksichtigt, auch wenn der zugrunde liegende Datenspeicher selbst nicht tabellarisch ist. Da die GetSubscribers-Methode ADO.NET Objekte für den Zugriff auf den zugrunde liegenden Datenspeicher verwendet, kann sie nicht auf einen hierarchischen Datenspeicher zugreifen, z. B. auf eine XML-Datei, in der Instanzen der Klassen in System.Xml und deren Unternamespaces anstelle von ADO.NET Objekten verwendet werden müssen.
Das Hauptproblem mit dem Datenzugriffscode, der in der GetSubscribers-Methode verwendet wird, besteht darin, dass er direkt den tatsächlichen Code enthält, der die Daten aus dem zugrunde liegenden Datenspeicher extrahiert. Dies ist genau die Art des Problems, das das .NET Framework 2.0-Anbietermuster speziell für die Lösung entwickelt hat. Gemäß dem Anbietermuster muss die GetSubscribers-Methode die Verantwortung für die Bereitstellung des Codes für den Zugriff auf den Datenspeicher an eine andere Klasse delegieren. Sie wird als Codeanbieterklasse bezeichnet. Der Typ der Codeanbieterklasse hängt vom Typ des Datenspeichers ab, auf den sie zugreift. Diese Codeanbieterklassen werden gemeinsam als Datenquellensteuerelemente bezeichnet. ASP.NET 2.0 enthält verschiedene Arten von Datenquellensteuerelementen, darunter SqlDataSource-, AccessDataSource-, ObjectDataSource-, XmlDataSource-und SiteMapDataSource--Steuerelemente.
Das SqlDataSource-Steuerelement wurde speziell für das Aktualisieren, Löschen, Einfügen und Extrahieren von Daten aus relationalen Datenspeichern wie Microsoft SQL Server und Oracle entwickelt. Das AccessDataSource-Steuerelement ist eine Unterklasse des SqlDataSource-Steuerelements, das weiß, wie sie mit Microsoft Access-Datenbanken arbeiten. Das ObjectDataSource-Steuerelement verwendet dagegen Speichergeschäftsobjekte als Datenspeicher. Das XmlDataSource-Steuerelement wurde speziell für das Extrahieren von Daten aus XML-Dokumenten entwickelt. Das XmlDataSource-Steuerelement bietet jedoch keinen Schreibzugriff auf das zugrunde liegende XML-Dokument.
Jedes Datenquellensteuerelement macht eine oder mehrere Ansichten des zugrunde liegenden Datenspeichers verfügbar. Jede Ansicht ist eine Instanz einer geeigneten Klasse. Beispielsweise machen die Steuerelemente SqlDataSource, AccessDataSource und ObjectDataSource Ansichten verfügbar, die Instanzen von SqlDataSourceView-, AccessDataSourceView-und ObjectDataSourceView- Klassen sind. Ansichten blenden den tatsächlichen Typ des zugrunde liegenden Datenspeichers aus und stellen sicher, dass sie wie der typ ist, den der Datenzugriffscode erwartet. Beispielsweise erwartet die GetSubscribers-Methode einen tabellarischen Datentyp des Datenspeichers, da sie ihre Clients tabellarische Daten einspeist. Die tabellarischen Ansichten ermöglichen es der GetSubscribers-Methode, tabellarische Daten aus dem zugrunde liegenden Datenspeicher zu extrahieren, auch wenn der Datenspeicher selbst eine hierarchische Datenquelle ist, z. B. ein XML-Dokument. Dadurch kann die GetSubscribers-Methode tabellarische und hierarchische Datenspeicher als tabellarische Datenspeicher behandeln.
Datenquellensteuerelemente können ihren Clients zwei Arten von Ansichten bereitstellen: tabellarisch und hierarchisch. ASP.NET 2.0 enthält zwei Datenquellensteuerelemente, die beide Arten von Ansichten, XmlDataSource und SiteMapDataSource bereitstellen. Die restlichen Datenquellensteuerelemente – SqlDataSource, AccessDataSource und ObjectDataSource – stellen nur tabellarische Ansichten dar. Sie können jedoch erweitert werden, um tabellarische und hierarchische Ansichten bereitzustellen.
Tabellarische Datenquellensteuerelemente
Ein tabellarisches Datenquellensteuerelement bewirkt, dass der zugrunde liegende Datenspeicher wie ein tabellarischer Datenspeicher fungiert, unabhängig davon, ob der Datenspeicher tabellarisch ist. Ein tabellarischer Datenspeicher besteht aus Tabellen mit Zeilen und Spalten, in denen jede Zeile ein Datenelement darstellt. Der Name einer Tabelle identifiziert und sucht die Tabelle unter anderen Tabellen im tabellarischen Datenspeicher. Eine tabellarische Ansicht fungiert wie eine Tabelle, was bedeutet, dass Ansichten benannt werden.
Wie bereits erwähnt, stellen jede Datenquellensteuerelementklasse und die zugehörige Ansichtsklasse (z. B. die SqlDataSource-Klasse und die zugehörige SqlDataSourceView-Klasse) den tatsächlichen Code zum Aktualisieren, Löschen, Einfügen und Extrahieren von Daten aus dem zugrunde liegenden Datenspeicher bereit. Offensichtlich ist der Code für jedes Datenquellensteuerelement und seine zugehörige Ansichtsklasse speziell für die Arbeit mit einem bestimmten Datentyp konzipiert. Daher sind jede Datenquellensteuerungsklasse und die zugehörige Ansichtsklasse datenspeicherspezifisch. Dies stellt ein ernsthaftes Problem mit der GetSubscribers-Methode dar, die Datenquellensteuerelemente und ihre tabellarischen Ansichten verwendet, um auf den zugrunde liegenden Datenspeicher zuzugreifen, da sie die Methode mit einem bestimmten Datentyp verknüpft, was bedeutet, dass dieselbe Methode nicht für den Zugriff auf verschiedene Datentypen von Datenspeichern verwendet werden kann.
ASP.NET 2.0 bietet eine Lösung, die das Anbietermuster verwendet, um:
- Einführung in die IDataSource Schnittstelle und abstrakte Klasse "DataSourceView"
- Ableiten aller tabellarischen Datenquellensteuerelemente von der IDataSource-Schnittstelle
- Ableiten aller tabellarischen Ansichten von der abstrakten DataSourceView-Klasse
Die IDataSource-Schnittstelle und die abstrakte DataSourceView-Klasse delegieren die Verantwortung für die Bereitstellung des tatsächlichen Codes zum Aktualisieren, Löschen, Einfügen und Extrahieren der Daten aus dem Datenspeicher in die entsprechenden Unterklassen. Der Datenzugriffscode, z. B. die GetSubscribers-Methode, muss die Methoden und Eigenschaften der IDataSource-Schnittstelle und der abstrakten DataSourceView-Klasse verwenden. Sie dürfen keine Methode oder Eigenschaft verwenden, die für eine bestimmte Datenquellensteuerelementklasse wie SqlDataSource oder eine bestimmte Datenquellenansichtsklasse wie SqlDataSourceView spezifisch ist. Das Anbietermuster ermöglicht es dem Datenzugriffscode, alle Datenquellensteuerelemente und ihre jeweiligen Datenquellenansichten auf generische Weise zu behandeln. Was den Datenzugriffscode betrifft, sind alle Datenquellensteuerelemente vom Typ "IDataSource" und alle Datenquellenansichten vom Typ "DataSourceView". Der Datenzugriffscode hat keine Möglichkeit, den tatsächlichen Typ des verwendeten Datenquellensteuerelements und des Datenquellenansichtsobjekts zu kennen. Dies führt zu einem neuen Problem. Wenn der Datenzugriffscode (die GetSubscribers-Methode) den Typ des Datenquellensteuerelements nicht kennt, wie kann er dann eine Instanz davon instanziieren?
Wie bereits erläutert, stellt das Anbietermuster eine Lösung bereit, die aus den folgenden Schritten besteht:
- Eine eindeutige Zeichenfolgen-ID wird verwendet, um jede Datenquellensteuerungsklasse zu identifizieren.
- Eine Textdatei (normalerweise eine XML-Datei) wird verwendet, um Informationen zu allen Datenquellensteuerelementklassen zu speichern.
- Ein Mechanismus wurde entwickelt und implementiert, der die XML-Datei nach einer Unterklasse mit einer bestimmten Zeichenfolgen-ID durchsucht.
Sehen wir uns nun an, wie ASP.NET 2.0 die oben genannten drei Aufgaben des Anbietermusters implementiert. ASP.NET 2.0 leitet alle Datenquellensteuerelemente von der klasse Control ab. Warum leiten Datenquellensteuerelemente von der Control-Klasse ab, wenn sie keinen HTML-Markuptext rendern? Sie werden von der Control-Klasse abgeleitet, damit sie die folgenden drei wichtigen Features erben können:
- Sie können deklarativ instanziiert werden.
- Sie speichern und wiederherstellen ihre Eigenschaftswerte über Postbacks hinweg.
- Sie werden der Steuerelementstruktur der enthaltenden Seite hinzugefügt.
Mit dem ersten Feature können Seitenentwickler Datenquellensteuerelemente in ihren jeweiligen .aspx Dateien deklarativ instanziieren. Daher fungiert die .aspx-Datei als Text- oder XML-Datei, die der zweite Schritt des Anbietermusters erfordert. Die ASP.NET 2.0-Steuerelementarchitektur erstellt dynamisch eine Instanz des deklarierten Datenquellensteuerelements und weist die Instanz einer Variablen zu, deren Name der ID-Eigenschaft des deklarierten Datenquellensteuerelements ist. Dies übernimmt die oben genannten ersten und dritten Schritte, die das Anbietermuster erfordert.
Abbildung 6 zeigt die Version der GetSubscribers-Methode, die die Methoden und Eigenschaften der IDataSource-Schnittstelle und der abstrakten DataSourceView-Klasse verwendet, um auf den zugrunde liegenden Datenspeicher zuzugreifen:
Abbildung 6. Die Version der GetSubscribers-Methode, die die Methoden und Eigenschaften der IDataSource-Schnittstelle und der abstrakten DataSourceView-Klasse verwendet
void GetSubscribers()
{
IDataSource ds = (IDataSource)MySource;
DataSourceView dv = ds.GetView(String.Empty);
DataSourceSelectArguments args = new DataSourceSelectArguments();
if (dv.CanSort)
args.SortExpression = "Email";
DataSourceViewSelectCallback callback = new DataSourceViewSelectCallback(SendMail);
dv.Select(args, callback);
}
Die erste Zeile der GetSubscribers-Methode zeigt eindeutig, dass die Methode das Datenquellensteuerelement als Objekt vom Typ "IDataSource" behandelt. Die Methode kümmert sich nicht und sollte sich nicht um den tatsächlichen Typ des Datenquellensteuerelements kümmern, z. B. ob es sich um ein SqlDataSource-, AccessDataSource-, ObjectDataSource- oder XmlDataSource-Steuerelement handelt. Dadurch können Seitenentwickler von einem Datenquellensteuerelement zu einem anderen wechseln, ohne den Datenzugriffscode (die GetSubscribers-Methode) ändern zu müssen. Im nächsten Abschnitt wird dieses wichtige Thema ausführlicher behandelt.
Die GetSubscribers-Methode ruft die GetView--Methode des IDataSource-Objekts auf, um auf das standardmäßige tabellarische Ansichtsobjekt zuzugreifen. Beachten Sie, dass die GetView-Methode ein Objekt vom Typ DataSourceView zurückgibt. Die GetSubscribers-Methode sollte sich nicht um den tatsächlichen Typ des Ansichtsobjekts kümmern, z. B. ob es sich um ein SqlDataSourceView-, AccessDataSourceView-, ObjectDataSourceView- oder XmlDataSourceView-Objekt handelt.
Als Nächstes erstellt die GetSubscribers-Methode eine Instanz der DataSourceSelectArguments Klasse, um zusätzliche Vorgänge wie Einfügen, Paging oder Abrufen der Gesamtzeilenanzahl für die Daten anzufordern, die vom Select-Vorgang zurückgegeben werden. Die Methode muss zuerst den Wert der CanInsert, CanPageoder CanRetrieveTotalRowCount -Eigenschaft der DataSourceView-Klasse überprüfen, um sicherzustellen, dass das Ansichtsobjekt den jeweiligen Vorgang unterstützt, bevor die Anforderung ausgeführt wird.
Da der Select-Vorgang asynchron ist, registriert die GetSubscribers-Methode die SendMail--Methode als Rückruf. Die Select-Methode ruft die SendMail-Methode automatisch auf, nachdem sie die Daten abfragt und die Daten als Argument übergibt, wie in Abbildung 7 dargestellt.
Abbildung 7. Die SendMail-Methode listet die Daten auf und extrahiert die erforderlichen Informationen.
void SendMail(IEnumerable data)
{
string firstName = String.Empty;
string lastName = String.Empty;
IEnumerator iter = data.GetEnumerator();
while (iter.MoveNext())
{
MailMessage message = new MailMessage();
message.From = "admin@greatnews.com";
message.To = DataBinder.Eval(iter.Current, "Email").ToString();
message.Subject = "NewsLetter";
firstName = DataBinder.Eval(iter.Current, "FirstName").ToString();
lastName = DataBinder.Eval(iter.Current, "LastName").ToString();
string mes = "Dear " + firstName + " " + lastName + ",<br/>";
mes += MessageBody.Text;
message.Body = mes;
message.BodyFormat = MailFormat.Html;
SmtpMail.SmtpServer = "<myserver>";
SmtpMail.Send(message);
}
}
Die SendMail-Methode ruft die GetEnumerator- Methode des Objekts auf, das als erstes Argument übergeben wird, um auf das Enumeratorobjekt zuzugreifen, und verwendet den Enumerator zum Aufzählen der Daten. Die SendMail-Methode verwendet die Eval-Methode der DataBinder- Klasse, um die E-Mail-Adresse, den Vornamen und den Nachnamen jedes Abonnenten zu extrahieren und den Nachrichtenbrief an jede person zu senden.
Wechseln von einem Datenquellensteuerelement zu einem anderen
Wie bereits erwähnt, erstellt die ASP.NET Steuerelementarchitektur dynamisch eine Instanz des Datenquellensteuerelements, das auf der jeweiligen .aspx Seite deklariert ist, und weist sie der Variablen zu, deren Name der Wert der -ID Eigenschaft des deklarierten Datenquellensteuerelements ist. Eine solche dynamische Instanziierung des deklarierten Datenquellensteuerelements isoliert die GetSubscribers-Methode vom tatsächlichen Typ des Datenquellensteuerelements und ermöglicht es der Methode, alle Datenquellensteuerelemente als Objekte vom Typ IDataSource zu behandeln. Auf diese Weise können Seitenentwickler von einem Typ der Datenquellensteuerung zu einem anderen wechseln, ohne den Datenzugriffscode (die GetSubscribers-Methode) zu ändern. In diesem Abschnitt wird ein Beispiel für einen solchen Fall dargestellt.
Angenommen, unsere Webanwendung verwendet die GetSubscribers-Methode in Verbindung mit dem ObjectDataSource-Steuerelement, um die Liste der Abonnenten aus einem relationalen Datenspeicher wie Microsoft SQL Server abzurufen:
<asp:SqlDataSource ID="MySource" Runat="Server"
ConnectionString="<%$ ConnectionStrings:MySqlConnectionString %>"
SelectCommand="Select * From Subscribers" />
Angenommen, unsere Webanwendung funktioniert in einer Umgebung, in der die Liste der Abonnenten möglicherweise auch aus einem XML-Dokument stammt. Das XML-Dokument kann eine lokale XML-Datei oder eine Remoteressource sein, auf die über die URL zugegriffen wird. Daher muss unsere Anwendung in der Lage sein, die Liste der Abonnenten aus XML-Dokumenten abzurufen. Das ObjectDataSource-Steuerelement ist offensichtlich nicht für das Abrufen tabellarischer Daten aus XML-Dokumenten konzipiert. Dies bedeutet, dass wir ein Datenquellensteuerelement verwenden müssen, das tabellarische Daten aus XML-Dokumenten abrufen kann, z. B. das XmlDataSource-Steuerelement.
Die GetSubscribers-Methode verwendet keine für die Klassen ObjectDataSource und ObjectDataSourceView spezifische Eigenschaft oder Methode und verwendet nur die Methoden und Eigenschaften der IDataSource-Schnittstelle und der abstrakten DataSourceView-Klasse, um mit Datenquellensteuerelementen zu arbeiten. Wir können ganz einfach von ObjectDataSource zum XmlDataSource-Steuerelement wechseln und denselben Datenzugriffscode wie die GetSubscribers-Methode verwenden, um die Liste der Abonnenten abzurufen:
<asp:XmlDataSource ID="MySource" Runat="Server"
DataFile="data.xml" XPath="/Subscribers/Subscriber" />
Der Wert des XPath-Attributs des XmlDataSource-Steuerelements wird auf "/Subscribers/Subscriber" festgelegt, um alle Abonnenten auszuwählen.
Einfüge- und Löschvorgang
Rückruf unserer Webanwendung besteht aus zwei Teilen. Im zweiten Teil der Anwendung können Benutzer eine Adressenliste abonnieren/kündigen. Die Subscribe-Methode wird aufgerufen, wenn auf die Schaltfläche Abonnieren geklickt wird, wie in Abbildung 8 dargestellt.
Abbildung 8. Die Subscribe-Methode wird aufgerufen, wenn auf die Schaltfläche "Abonnieren" geklickt wird.
void Subscribe(Object sender, EventArgs e)
{
IDataSource ds = (IDataSource)MySource;
DataSourceView dv = ds.GetView(String.Empty);
KeyedList values = new KeyedList();
values.Add("Email", Email.Text);
values.Add("FirstName", FirstName.Text);
values.Add("LastName", LastName.Text);
DataSourceViewOperationCallback callback = new DataSourceViewOperationCallback(OperationCallback);
if (dv.CanInsert)
dv.Insert(values, callback);
}
Die erste Zeile der Subscribe-Methode zeigt, dass sich die Methode nicht um den tatsächlichen Typ des Datenquellensteuerelements kümmert. Daher können wir zu einem anderen Datenquellensteuerelement wechseln, um einen neuen Datenspeicher zu unterstützen, ohne den Code in der Subscribe-Methode ändern zu müssen.
Die Methode verwendet eine Instanz des KeyedList- Klasse, um die E-Mail, den Vornamen und den Nachnamen des Abonnenten zu erfassen. Wir müssen die KeyedList-Klasse nicht verwenden. Wir können jede Klasse verwenden, die die IDictionary Schnittstelle implementiert, einschließlich ArrayList, KeyedList usw.
Die Subscribe-Methode überprüft den Wert der CanInsert Eigenschaft des Datenquellenansichtsobjekts, um sicherzustellen, dass das Ansichtsobjekt den vorgang Einfügen unterstützt, bevor die Insert-Methode aufgerufen wird. Die Subscribe-Methode übergibt die KeyedList-Instanz als erstes Argument an die Insert-Methode.
Die Unsubscribe-Methode funktioniert ähnlich wie die Subscribe-Methode. Der Hauptunterschied besteht darin, dass die Unsubscribe-Methode die Delete Methode des jeweiligen Ansichtsobjekts aufruft, um das Abonnement aus dem zugrunde liegenden Datenspeicher zu entfernen, wie in Abbildung 9 dargestellt.
Abbildung 9. Die Unsubscribe-Methode wird aufgerufen, wenn auf die Schaltfläche "Abonnement" geklickt wird.
void Unsubscribe(Object sender, EventArgs e)
{
IDataSource ds = (IDataSource)MySource;
DataSourceView dv = ds.GetView(String.Empty);
KeyedList keys = new KeyedList();
keys.Add("Email", Email.Text);
KeyedList oldValues = new KeyedList();
oldValues.Add("Email", Email.Text);
DataSourceViewOperationCallback callback = new DataSourceViewOperationCallback(OperationCallback);
if (dv.CanDelete)
dv.Delete(keys, oldValues, callback);
}
Hierarchische Datenquellensteuerelemente
Die GetSubscribers-Methode (der Datenzugriffscode) leitet tabellarische Daten an die Aufrufer weiter. Es gibt jedoch Situationen, in denen Datenzugriffscode hierarchische Daten an die Aufrufer zurückgeben müssen. In diesem Abschnitt wird die Implementierung der Version der GetSubscribers-Methode dargestellt, die hierarchische Daten zurückgibt. Wir können uns dies als Vertrag zwischen der Methode und ihren Aufrufern vorstellen. Die Aufrufer erwarten, dass die Methode hierarchische Daten aus hierarchischen und tabellarischen Datenspeichern zurückgibt.
ASP.NET 2.0 verwendet das Anbietermuster, um die GetSubscribers-Methode vom tatsächlichen Typ des zugrunde liegenden Datenspeichers zu isolieren und die Methode mit den hierarchischen Ansichten des Datenspeichers darzustellen. Dadurch kann die Methode sowohl hierarchische als auch tabellarische Datenspeicher als hierarchische Datenspeicher behandeln.
Jedes hierarchische Datenquellensteuerelement ist speziell für die Arbeit mit einem bestimmten Datenspeicher konzipiert. Da jedoch alle hierarchischen Datenquellensteuerelemente die IHierarchicalDataSource Schnittstelle implementieren und alle hierarchischen Datenquellenansichten von der HierarchischeDataSourceView--Klasse abgeleitet sind, muss die GetSubscribers-Methode nicht mit den Besonderheiten jedes Datenquellensteuerelements umgehen und alle diese auf generische Weise behandeln.
IHierarchicalEnumerable GetSubscribers()
{
IHierarchicalDataSource ds = (IHierarchicalDataSource)MySource;
HierarchicalDataSourceView dv = ds.GetHierarchicalView("/Subscribers");
return dv.Select();
}
Die erste Zeile der GetSubscribers-Methode zeigt, dass die Methode das Datenquellensteuerelement als Objekt vom Typ "IHierarchicalDataSource" behandelt und sich nicht um den tatsächlichen Typ des Datenquellensteuerelements kümmert. Dadurch können Seitenentwickler zu einem neuen hierarchischen Datenquellensteuerelement wechseln, um Unterstützung für einen neuen Datenspeicher hinzuzufügen, ohne den Code in der GetSubscribers-Methode ändern zu müssen.
Die GetSubscribers-Methode ruft dann die GetHierarchicalView--Methode der HierarchischenDataSourceView-Klasse auf, um mit dem angegebenen Pfad, z. B. "/Abonnenten", auf die hierarchische Ansicht zuzugreifen. Beachten Sie, dass die Select-Methode nicht asynchron ist. Die Anwendung übergibt die von der GetSubscribers-Methode zurückgegebenen Daten an die SendMail-Methode (siehe Abbildung 15). Beachten Sie, dass die Daten vom Typ IHierarchicalEnumerablesind.
Die IHierarchicalEnumerable implementiert IEnumerable, was bedeutet, dass die GetEnumerator--Methode verfügbar gemacht wird. Die SendMail-Methode ruft die GetEnumerator-Methode auf, um auf das jeweilige IEnumerator-Objekt zuzugreifen, das anschließend zum Aufzählen der Daten verwendet wird. Die IHierarchicalEnumerable macht auch eine Methode namens GetHierarchyData verfügbar, die das aufgezählte Objekt verwendet und die IHierarchyData- zugeordnete Objekt zurückgibt.
Die IHierarchyData-Schnittstelle macht eine wichtige Eigenschaft namens Itemverfügbar, die nichts anderes als das Datenelement ist. Die SendMail-Methode verwendet die Eval-Methode der XPathBinder Klasse, um XPath-Ausdrücke für das Item-Objekt auszuwerten.
Abbildung 10. Die SendMail-Methode listet die Daten auf, extrahiert die erforderlichen Informationen und sendet den Newsletter an jeden Abonnenten.
void SendMail(IHierarchicalEnumerable data)
{
string firstName = String.Empty;
string lastName = String.Empty;
IEnumerator iter = data.GetEnumerator();
while (iter.MoveNext())
{
IHierarchyData ihdata = data.GetHierarchyData(iter.Current);
MailMessage message = new MailMessage();
message.From = "admin@subscribers.com";
message.To = XPathBinder.Eval(ihdata.Item, "@Email").ToString();
message.Subject = "NewsLetter";
firstName = XPathBinder.Eval(ihdata.Item, "@FirstName").ToString();
lastName = XPathBinder.Eval(ihdata.Item, "@LastName").ToString();
string mes = "Hi " + firstName + " " + lastName + ",<br/>";
mes += MessageBody.Text;
message.Body = mes;
message.BodyFormat = MailFormat.Html;
SmtpMail.SmtpServer = "MyServer";
SmtpMail.Send(message);
}
}
Schlussfolgerung
Mithilfe eines schrittweisen Ansatzes, der verschiedene ASP.NET 2.0 und ADO.NET 2.0-Tools und -Techniken zeigt, wie Seitenentwickler generischen Datenzugriffscode schreiben können, der für den Zugriff auf verschiedene Datentypen verwendet werden kann.
Dr. Shahram Khosraviist senior Software Engineer mit Schlumberger Information Solutions (SIS). Shahram ist spezialisiert auf ASP.NET, XML-Webdienste, .NET-Technologien, XML-Technologien, 3D-Computergrafiken, HI/Usability, Design Patterns und entwickeln ASP.NET Serversteuerelemente und -komponenten. Er verfügt über mehr als 10 Jahre Erfahrung in objektorientierter Programmierung. Er verwendet verschiedene Microsoft-Tools und -Technologien wie SQL Server und ADO.NET. Shahram hat Artikel über .NET und ASP.NET Technologien für asp.netPRO Magazine geschrieben.