Within the .NET Framework, data access primarily falls within three general categories: -
File-based data storage Also called a flat file . Handled by the System.IO namespace. -
Relational database storage Handled by the System.Data , System.Data.SqlClient , and System.Data.OleDb namespaces that make up ADO.NET. -
XML data files Handled by the System.Xml namespace. Disk Files or Flat Files One of the oldest forms of storage still in common use involves the storage of data within simple disk files, often referred to as flat files to distinguish them from the more structured relational database and hierarchical XML storage models. Browsing When your application needs to include external file data, it is often useful to allow users to browse to the desired file's location. This is accomplished by invoking an instance of the System.Windows.Forms.OpenFileDialog class, as shown here: -
Open a Visual Basic .NET Windows Application project with a form. -
Place a Button control (btnBrowse) and a TextBox control (txtFilename) on the form. -
Open the form's code module and add the following: Imports System.Windows.Forms Private Sub btnBrowse_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnBrowse.Click Dim dlgOpen As New OpenFileDialog() dlgOpen.Title = "Select a File" dlgOpen.Filter = "Text files (*.txt)*.txtAll Files(*.*)*.*" If dlgOpen.ShowDialog = DialogResult.OK Then txtFilename.Text = dlgOpen.FileName End If End Sub -
Set the form as the startup object for the project and then run the project. When you click the button, the File Open dialog box will open. Once you have selected a file and clicked OK, the name of the file will be displayed in the text box. Streams and Backing Classes File-based data access involves two primary concepts: -
Stream A flow of raw data -
Backing store A place to put data, such as a file, network connection, Internet address, or even a section of memory The System.IO namespace includes several classes suitable for accessing and manipulating streams and backing stores, including the following: -
The FileStream class This class treats a file simply as a stream of bytes. -
The StreamReader class This class is used to read text data line by line. -
The StreamWriter class This class is used to write text data line by line. -
The BinaryReader class This class is used to read binary data by individual data types, which must match the types used to write the data exactly. -
The BinaryWriter class This class is used to write binary data by individual data types. Table 6.1 includes some of the important members of the FileStream object class. Table 6.1. Important Members of the FileStream Object Member | Type | Description | CanRead | Property | This property returns True if you can read from the FileStream object. | CanSeek | Property | This property returns True if you can seek to a particular location in this FileStream object. | CanWrite | Property | This property returns True if you can write to this FileStream object. | Close | Method | Closes the FileStream object and releases associated resources. | Flush | Method | Writes any buffered data to the backing store. | Length | Property | The length of the FileStream object in bytes. | Position | Property | Returns the position within the FileStream object. | Read | Method | Reads a sequence of bytes. | ReadByte | Method | Reads a single byte. | Seek | Method | Sets the FileStream object to a specified position. | Write | Method | Writes a sequence of bytes. | WriteByte | Method | Writes a single byte. | The FileStream class treats a file as a stream of bytes. For example, you can use this class as a way to make a backup copy of a file. You might use this code snippet in conjunction with an OpenFileDialog object to back up a file selected by the user : ' Browse for a file to back up Dim dlgOpen As New OpenFileDialog() dlgOpen.Title = "Select a File to back up" dlgOpen.Filter = "All Files(*.*)*.*" If dlgOpen.ShowDialog = DialogResult.OK Then ' Open the file for reading as a stream Dim fsIn As FileStream = File.OpenRead(dlgOpen.FileName) ' Open the file for writing as a stream Dim fsOut As FileStream = File.OpenWrite(dlgOpen.FileName & ".bak") ' Copy all data from in to out, byte-by-byte Dim b As Int16 Do While ((b = fsIn.ReadByte()) > -1) fsOut.WriteByte(CType(b, Byte)) Loop ' Clean up fsOut.Flush() fsOut.Close() fsIn.Close() End If Using the StreamReader and StreamWriter Classes The FileStream class is your best option when you don't care (or don't know) about the internal structure of the files with which you're working. But in many cases, you have additional knowledge that lets you use other objects. Text files, for example, are often organized as lines of text separated by end-of-line characters . The StreamReader and StreamWriter classes provide you with tools for manipulating such files. Here's how you might use a StreamWriter to write text and then read the same text with a StreamReader : ' Create a new file to work with Dim fsOut As FileStream = File.Create("c:\temp\test.txt") ' Create a StreamWriter to handle writing Dim sw As StreamWriter = New StreamWriter(fsOut) ' And write some data sw.WriteLine("Mary had a little lamb") sw.WriteLine("Its fleece was white as snow") sw.Flush() sw.Close() ' Now open the file for reading Dim fsIn As FileStream = File.OpenRead("c:\temp\test.txt") ' Create a StreamReader to handle reading Dim sr As StreamReader = New StreamReader(fsIn) ' And read the data Do While sr.Peek > -1 lbLines.Items.Add(sr.ReadLine()) Loop sr.Close() You can think of the StreamWriter and StreamReader classes as forming an additional layer of functionality on top of the FileStream class. The FileStream object handles opening a particular disk file and then serves as a parameter to the constructor of the StreamWriter or StreamReader . This code first opens a StreamWriter and calls its WriteLine method multiple times to write lines of text to the file. It then creates a StreamWriter that uses the same text file. The code makes use of the Peek method of the StreamReader to watch for the end of the file. This method returns the next byte in the file without actually reading it (or 1 if there is no more data to be read). As long as there's data to read, the ReadLine method of the StreamReader can read it to place it in the list box. In addition to the methods you see in this example, the StreamWriter has a Write method that writes output without adding a newline character. The StreamReader class implements Read and ReadToEnd methods that offer additional functionality for reading data. The Read method reads a specified number of characters. The ReadToEnd method reads all the remaining characters to the end of the stream. | The Framework Class Library also contains TextReader and TextWriter classes. These are general implementations for reading and writing character data. The StreamReader and StreamWriter derive from these general implementations to manage line-by-line input and output. | Using the BinaryReader and BinaryWriter Classes For files with a known internal structure, the BinaryReader and BinaryWriter classes offer streaming functionality that's oriented toward particular data types. Here's a code example that uses these two classes: ' Create a new file to work with Dim fsOut As FileStream = File.Create("c:\temp\test.dat") ' Create a BinaryWriter to handle writing Dim bw As BinaryWriter = New BinaryWriter(fsOut) ' And write some data Dim intData1 As Integer = 5 Dim dblData2 As Decimal = 22.478 Dim strData3 As String = "Some sample text" bw.Write(intData1) bw.Write(dblData2) bw.Write(strData3) bw.Flush() bw.Close() ' Now open the file for reading Dim fsIn As FileStream = File.OpenRead("c:\temp\test.dat") ' Create a BinaryReader to handle reading Dim br As BinaryReader = New BinaryReader(fsIn) ' And read the data lbData.Items.Add("Integer: " & br.ReadInt32()) lbData.Items.Add("Decimal: " & br.ReadDecimal()) lbData.Items.Add("String: " & br.ReadString()) br.Close() BinaryWriter and BinaryReader are oriented toward writing and reading particular types of data. The BinaryWriter.Write method has overloads for many data types, so it can handle writing almost anything to a file. The BinaryReader class has methods for reading all those different data types; this code shows the ReadInt32 , ReadDecimal , and ReadStringmethods methods in action. | You must read exactly the same data types with BinaryReader that you originally wrote with BinaryWriter . Otherwise, you'll get back garbage in your read data. | The ADO.NET Object Model ADO.NET is what describes the collection of namespaces used to access data within relational databases. The ADO.NET object model is separated into two primary types of objects: Data Provider Classes Visual Studio .NET ships with several data providers, including SQL Server ( System.Data.SqlClient ) and OLE DB ( System.Data.OleDb ). Each of these namespaces provides access to variations of the standard data provider classes: Connection , Command , Parameter , DataReader , and DataAdapter . | Although this section examines the classes provided by the SQL Server data provider ( SqlConnection, SqlCommand, SqlParameter, SqlDataReader , and SqlDataAdapter ), there are implementations of the same classes within the OLE DB data provider as well ( OleDbConnection, OleDbCommand, OleDbParameter, OleDbDataReader , and OleDbDataAdapter ). You can use the OLE DB data provider classes for data sources, such as Microsoft Access databases, that are available via OLE DB. | The Connection Class The Connection class represents a single persistent connection to a data source. The SQL Server implementation of this class is the SqlConnection class. Table 6.2 details some of the more important members of this class. Table 6.2. Important Members of the SqlConnection Class Member | Type | Description | BeginTransaction | Method | Starts a new transaction | Close | Method | Returns the Connection object to the pool | ConnectionString | Property | The server used by this Connection object | CreateCommand | Method | A new SqlCommand object executing through this Connection object | Open | Method | Opens the Connection object | | When a connection is closed, it is simply returned to a pool of available connections to later be reused. | The Command >and Parameter Classes A Command class represents an executable action, whereas a Parameter class represents a single value passed to a stored procedure. These classes are implemented within the SQL Server data provider as the SqlCommand and SqlParameter classes. Table 6.3 details some of the more important members of the SqlCommand class. Table 6.3. Important Members of the SqlCommand Class Member | Type | Description | CommandText | Property | A statement executed by the Command object | CommandType | Property | Enumerated type of command this SqlCommand object represents | Connection | Property | The connection through which the Command object executes | CreateParameter | Method | Creates a new Parameter object | ExecuteNonQuery | Method | Executes a Command object that does not return a result set | ExecuteReader | Method | Executes a Command object and returns the results in a DataReader object | ExecuteScalar | Method | Executes a Command object and returns the first column of the first row of the result set | ExecuteXmlReader | Method | Executes a Command object and returns the results in an XmlReader object | Parameters | Property | A collection of Parameter objects for this Command object | The DataReader Class When the ExecuteReader method of the Command class is called, the result set is returned in a DataReader object. This read-only object can only be accessed sequentially in order from first to last, but it's returned very quickly. The DataReader class is implemented within the SQL Server data provider as the SqlDataReader class. The methods of the DataReader class return values from a single record at a time, or a single element at a time from a specified column. | It is very important to call the DataReader.Close method when you are done in order to release the Connection object for other uses. Alternatively, you can specify when you call the ExecuteReader method that it is to release the Connection object when you're finished. | The DataAdapter Class The DataAdapter class provides a two-way data transfer conduit between data provider and DataSet classes. Within the SQL Server data provider, this object is implemented as the SqlDataAdapter class. Some of the more important members of this class are detailed in Table 6.4. Table 6.4. Important Members of the SqlDataAdapter Class Member | Type | Description | DeleteCommand | Property | Specifies a Command object to be executed for all deleted rows | Fill | Method | Copies data from the data source to a DataSet object | InsertCommand | Property | Specifies a Command object to be executed for all new rows | SelectCommand | Property | Retrieves data rows from the source | Update | Method | Copies data from a DataSet object back to the data source | UpdateCommand | Property | Specifies a Command object to be executed for all modified rows | DataSet Classes When manipulating data derived from a relational database, it is often useful to create a memory-resident copy of the target relational data that can be manipulated locally, with changes posted back to the data source through an update action when the edit is completed. In the .NET Framework, the DataSet class provides such a memory-resident database copy. The DataSet class provides access to a virtual relational data set that includes its own tables, views, and relationships, making it possible to pass relational data between controls and components in your application without requiring a persistent connection to the data source. Table 6.5 details some of the more important members of the DataSet class. Table 6.5. Important Members of the DataSet Class Member | Type | Description | AcceptChanges | Method | Accepts all changes in the DataSet object | Clear | Method | Removes all data from the DataSet object | GetChanges | Method | Returns a DataSet object containing only the changed data in the DataSet | GetXml | Method | Returns the DataSet object in XML format | GetXmlSchema | Method | Returns an XML Schema Design (XSD) representation of the DataSet object | Merge | Method | Combines two DataSet objects | ReadXml | Method | Inputs data from an XML file to the DataSet object | ReadXmlSchema | Method | Inputs the schema from an XSD file | Relations | Property | A collection of DataRelation objects | Tables | Property | A collection of DataTable objects | WriteXml | Method | Outputs the DataSet object to an XML file | WriteXmlSchema | Method | Outputs the DataSet object's schema to an XSD file | The DataTable Class Each table within a DataSet is represented by a DataTable object, which may be manipulated by row or by column as desired. Table 6.6 details some of the more important members of the DataTable class. You may recognize many of the members as being very similar to structures within a relational database. Table 6.6. Important Members of the DataTable Class Member | Type | Description | ChildRelations | Property | A collection of child DataRelation objects | Clear | Method | Removes all data from the DataTable object | ColumnChanged | Event | Fires after a change to data in any row of the specified column | ColumnChanging | Event | Fires just before a change to data in any row of the specified column | Columns | Property | A collection of DataColumn objects | Constraints | Property | A collection of Constraint objects | NewRow | Method | Adds a new blank row | ParentRelations | Property | A collection of parent DataRelation objects | PrimaryKey | Property | An array of DataColumn objects that form the DataTable object's primary key | RowChanged | Event | Fires just after a change to data in the specified DataRow object | RowChanging | Event | Fires just before a change to data in the specified DataRow object | RowDeleted | Event | Fires just after a row deletion | RowDeleting | Event | Fires just before a row deletion | Rows | Property | A collection of DataRow objects | Select | Method | Returns an array of DataRow objects filtered by the specified criteria | TableName | Property | The DataTable object's name | When you're using the Select method of a DataTable , the filter expression is a SQL WHERE clause that follows these rules: -
Column names containing special characters or white space are enclosed by square brackets ( [ ] ). -
String constants are enclosed by single quotes ( ' ). -
Date constants are enclosed in pound signs ( # ). -
Expressions can be created using the AND , OR , NOT , IN , and LIKE keywords, parenthetical ordering, and comparison and arithmetic operators. -
String values are concatenated (combined) using the plus sign ( + ). -
Wildcard values ( * and % ) may only be used at the beginning or end of test string values. -
Child table columns can be referenced using Child.Column . Child(RelationName).Column can be used to choose a particular child table if there are several. -
You may use the Sum , Avg , Min , Max , Count , StDev , and Var aggregate functions with child tables. -
You may use the CONVERT , LEN , ISNULL , IIF , and SUBSTRING functions. It is also possible to order the resulting result set by providing a sort order expression. You can also restrict the returned rows in the result set by specifying one of the DataViewRowState enumerated values, as detailed in Table 6.7. Table 6.7. The DataViewRowState Enumerated Values Value | Meaning | Added | Returns only new rows not yet committed | CurrentRows | Returns all current rows | Deleted | Returns only deleted rows | ModifiedCurrent | Returns only modified rows | ModifiedOriginal | Returns the original data from the modified rows | None | Returns an empty result set | OriginalRows | Returns all original data, including modified values and deleted rows | Unchanged | Returns only unmodified rows | The DataRelation Class A DataRelation object represents each relationship between tables within a DataSet . Table 6.8 details some of the more important members of the DataRelation class. Table 6.8. Important Members of the DataRelation Class Member | Type | Description | ChildColumns | Property | A collection of DataColumn objects defining the foreign key | ChildKeyConstraint | Property | A ForeignKeyConstraint object for the relation | ChildTable | Property | A DataTable object linked by the foreign key (child DataTable ) | ParentColumns | Property | A collection of DataColumn objects defining the primary key | ParentKeyConstraint | Property | A PrimaryKeyConstraint object for the relation | ParentTable | Property | A DataTable object linked by the primary key (parent DataTable ) | RelationName | Property | The DataRelation object's name | The DataView Class The DataView class within a DataSet fills the role a view would fill in a relational database. That is, the DataView presents an additional way to look at data that's actually stored in DataTable objects. A DataView object may be used to aggregate or filter the rows returned in its result set, using standard SQL expressions or state-change filtering. Table 6.9 details some of the more important members of this class. Table 6.9. Important Members of the DataView Class Member | Type | Description | AddNew | Method | Adds a new row. | AllowDelete | Property | This property returns True if the DataView object allows deletions. | AllowEdit | Property | This property returns True if the DataView object allows updates. | AllowNew | Property | This property returns True if the DataView object allows the insertion of new rows. | Count | Property | The number of rows in the DataView object. | Delete | Method | Deletes an existing row. | Find | Method | Searches for a particular row. | FindRows | Method | Returns an array of rows filtered by the provided expression. | Item | Property | Returns a particular row as a DataRowView object. | Sort | Method | Sorts the rows in a DataView object. | The DataRow Class A DataTable object represents a collection of DataRow objects, which serve the same role as records within a relational database. Each row may have one or more elements arranged within a columnar format in the table. Table 6.10 details some of the more important members of the DataRow class. Table 6.10. Important Members of the DataRow Class Member | Type | Description | BeginEdit | Method | Initiates editing of the DataRow object | CancelEdit | Method | Discards a pending edit | Delete | Method | Deletes a row | EndEdit | Method | Saves a pending edit's changes | Item | Property | Returns the value of the specified column in the row | IsNull | Method | True if the specified column contains a Null value | RowState | Property | Returns the current state of a DataRow object, which may be tested against the DataViewRowState enumerated values | The DataColumn Class A DataTable object also includes a collection of DataColumn objects, which serve the same role as individual elements within a relational database. Each column may be independently configured with a length, a specific data type, and constraints on allowable values. Table 6.11 details some of the more important members of the DataColumn class. Table 6.11. Important Members of the DataColumn Class Member | Type | Description | AllowDbNull [*] | Property | True if the column allows Null values | AutoIncrement | Property | True if the column has the identity constraint | ColumnName | Property | The DataColumn object's name | DataType | Property | The data type stored in the column | DefaultValue | Property | The default value assigned to this column when a new row is inserted | MaxLength | Property | The maximum length of the value stored in the column | Unique | Property | True if values in the column must be unique across all rows | [*] A Null value is not the same thing as a numeric zero or zero-length string value. Null is a special value that represents completely unknown data and may not be used in some types of comparative evaluation without additional testing. Using a DataSet Object You may create a form capable of displaying a multitable DataSet by performing the following steps: -
Open a Visual Basic .NET Windows Application project with a form. -
Place a Button control (btnLoadData) and a DataGrid control (dgMain) on the form. -
Open the form's code module and add the following: Imports System.Data Imports System.Data.SqlClient Private Sub btnLoadData_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnLoadData.Click Dim cnn As SqlConnection = _ New SqlConnection("Data Source=(local);" & _ "Initial Catalog=Northwind;Integrated Security=SSPI") Dim ds As DataSet = New DataSet() Dim cmdCustomers As SqlCommand = cnn.CreateCommand() cmdCustomers.CommandType = CommandType.Text cmdCustomers.CommandText = "SELECT * FROM Customers" Dim daCustomers As SqlDataAdapter = New SqlDataAdapter() daCustomers.SelectCommand = cmdCustomers daCustomers.Fill(ds, "Customers") Dim cmdOrders As SqlCommand = cnn.CreateCommand() cmdOrders.CommandType = CommandType.Text cmdOrders.CommandText = "SELECT * FROM Orders" Dim daOrders As SqlDataAdapter = New SqlDataAdapter() daOrders.SelectCommand = cmdOrders daOrders.Fill(ds, "Orders") Dim cmdOrderDetails As SqlCommand = cnn.CreateCommand() cmdOrderDetails.CommandType = CommandType.Text cmdOrderDetails.CommandText = "SELECT * FROM [Order Details]" Dim daOrderDetails As SqlDataAdapter = New SqlDataAdapter() daOrderDetails.SelectCommand = cmdOrderDetails daOrderDetails.Fill(ds, "OrderDetails") Dim relCustOrder As DataRelation = _ ds.Relations.Add("CustOrder", _ ds.Tables("Customers").Columns("CustomerID"), _ ds.Tables("Orders").Columns("CustomerID")) Dim relOrderOrderDetails As DataRelation = _ ds.Relations.Add("OrderOrderDetails", _ ds.Tables("Orders").Columns("OrderID"), _ ds.Tables("OrderDetails").Columns("OrderID")) dgMain.DataSource = ds dgMain.DataMember = "Customers" End Sub -
Set the form as the startup object for the project and then run the project. When you click the button, the application will access the Northwind database on SQL Server running on the local system using integrated Windows authentication, displaying Customers' data within the DataGrid , as shown in Figure 6.3. Note that by using a multitable DataSet you can preserve the individual rows of data and the relationships between them. If you returned all the data by using a SQL JOIN query, it would "flatten" the results and make them appear to come from one big table. Figure 6.3. Customers' data from a multitable DataSet . XML Data XML data files make up the third common source of data your applications may use. XML stands for Extensible Markup Language , which uses human-readable , highly structured files that include both data as well as a description of the data (referred to as metadata). An XML Schema Description (XSD) file may be used to provide details on the schema used within an XML file. XML Example The following is a sample XML document: <?xml version="1.0" encoding="UTF-8"?> <Customers> <Customer CustNumber="1"> <CustName>Performance Wheels</CustName> <CustCity>Dallas</CustCity> <CustState>TX</CustState> </Customer> <Customer CustNumber="2"> <CustName><![CDATA[Larry's Grille]]></CustName> <CustCity>Austin</CustCity> <CustState>TX</CustState> </Customer> </Customers> The first line of this sample file is the XML declaration, which details the version of the XML specification and encoding used. Data within the file is organized by nested elements, each level bracketed by a pair of tags, as shown here: <Tag>element data</Tag> Here's a shortcut for immediately closing a tag: <Tag /> | Tag names may be anything you like, provided each tag name starts with a letter or an underscore and contains only alphanumeric characters, underscores, hyphens, or periods. Tag names may not include white space and are case sensitive. | Elements may contain other elements and may also have attributes containing values. The sample code includes an attribute named CustNumber for each of the Customer elements. The CustName element in the second Customer record includes a [CDATA()] designation, which allows the customer name to include the apostrophe (') special character. XML files may also use entity references to refer to some special characters: -
A less-than character (<) can be represented by < . -
A greater-than character (>) can be represented by > . -
An ampersand character (&) can be represented by & . -
An apostrophe character (') can be represented by ' . -
A quotation mark character (") can be represented by " . The XmlDocument Class The .NET Framework includes support for XML files through the System.Xml namespace, which includes the XmlDocument class. This class can represent a complete XML file, including XmlNode objects for each element or attribute within the XML source document. Table 6.12 details some of the more important members of the XmlDocument class. Table 6.12. XmlDocument Class Members Member | Type | Description | CreateAttribute | Method | Creates an attribute node | CreateElement | Method | Creates an element node | CreateNode | Method | Creates an XmlNode object | DocumentElement | Property | Returns an XmlNode object for the root node | GetElementsByTagName | Method | Lists all elements with the specified tag name | Load | Method | Inputs an XML document | LoadXml | Method | Inputs an XML string | Save | Method | Outputs the XmlDocument object to a file or stream | WriteTo | Method | Outputs the XmlDocument object to an XmlWriter object | The XmlDataDocument Class The XmlDataDocument class extends the XmlDocument class to support DataSet synchronization. You may practice using an XML file as a relational data source by completing the following steps: -
Open a Visual Basic .NET Windows Application project with a form. -
Place a Button control (btnBrowse) and a DataGrid control (dgXml) on the form. -
Open the form's code module and add the following at the top of the code module: Imports System.Xml Private Sub btnBrowse_Click(ByVal sender As System.Object, _ ByVal e As System.EventArgs) Handles btnBrowse.Click Dim dlgOpen As New OpenFileDialog() dlgOpen.Title = "Select a File" dlgOpen.Filter = "XML files (*.xml)*.xmlAll Files(*.*)*.*" If dlgOpen.ShowDialog = DialogResult.OK Then Dim xtr As New XmlTextReader(dlgOpen.FileName) Dim xdd As XmlDataDocument = New XmlDataDocument() Dim ds As DataSet = xdd.DataSet ds.ReadXmlSchema(xtr) xtr.Close() xtr = New XmlTextReader(dlgOpen.FileName) xtr.WhitespaceHandling = WhitespaceHandling.None xdd.Load(xtr) xtr.Close() dgXml.DataSource = ds End If End Sub -
Set the form as the startup object for the project and then run the project. When you click the button, the File Open dialog box will open. Once you have selected an XML file and clicked OK, the contents of the file will be displayed in the DataGrid . As you can see in the preceding code, the XmlDataDocument class allows you to treat an XML file as if it were a set of tables from a database. The ReadXmlSchema method uses the information in the XML file to create the schema (tables and columns) of the matching database. The Load method loads the XML data into those tables and columns. As soon as you've done that, the DataSet object associated with the XmlDataDocument object will present a relational view of the XML data. |