114
A Multithreaded Programming
lthough C# contains many exciting features, one of its most powerful is its built-in support for multithreaded programming. A multithreaded program contains two or more parts that can run concurrently. Each part of such a program is called a thread,
and each thread defines a separate path of execution. Thus, multithreading is a specialized form of multitasking.
Multithreaded programming relies on a combination of features defined by the C# language and by classes in the .NET Framework. Because support for multithreading is built into C#, many of the problems associated with multithreading in other languages are minimized or eliminated.
Multithreading Fundamentals There are two distinct types of multitasking: process-based and thread-based. It is important to understand the difference between the two. A process is, in essence, a program that is executing. Thus, process-based multitasking is the feature that allows your computer to run two or more programs concurrently. For example, process-based multitasking allows you to run a word processor at the same time you are using a spreadsheet or browsing the Internet. In process-based multitasking, a program is the smallest unit of code that can be dispatched by the scheduler.
A thread is a dispatchable unit of executable code. The name comes from the concept of a “thread of execution.” In a thread-based multitasking environment, all processes have at least one thread, but they can have more. This means that a single program can perform two or more tasks at once. For instance, a text editor can be formatting text at the same time that it is printing, as long as these two actions are being performed by two separate threads.
The differences between process-based and thread-based multitasking can be summarized like this: Process-based multitasking handles the concurrent execution of programs. Thread- based multitasking deals with the concurrent execution of pieces of the same program.
A thread can be in one of several states. In general terms, it can be running. It can be ready to run as soon as it gets CPU time. A running thread can be suspended, which is a temporary halt to its execution. It can later be resumed. A thread can be blocked when waiting for a resource. A thread can be terminated, in which case its execution ends and cannot be resumed.
The .NET Framework defines two types of threads: foreground and background. By default, when you create a thread, it is a foreground thread, but you can change it to a background thread. The only difference between foreground and background threads is that a background thread will be automatically terminated when all foreground threads in its process have stopped.
All processes have at least one thread of execution, which is usually called the main thread because it is the one that is executed when your program begins. Thus, the main thread is the thread that all of the preceding example programs in the book have been using. From the main thread, you can create other threads.
The classes that support multithreaded programming are defined in the System.Threading namespace. Thus, you will usually include this statement at the
115
start of any multithreaded program:
using System.Threading;
The Thread Class The multithreading system is built upon the Thread class, which encapsulates a thread of execution. The Thread class is sealed, which means that it cannot be inherited. Thread defines several methods and properties that help manage threads. Throughout this chapter, several of its most commonly used members will be examined.
Creating and Starting a Thread There are a number of ways to create and start a thread. This section describes the basic mechanism. Various options are described later in this chapter.
To create a thread, instantiate an object of type Thread, which is a class defined in
System.Threading. The simplest Thread constructor is shown here:
public Thread(ThreadStart entryPoint)
Here, entryPoint is the name of the method that will be called to begin execution of the thread. ThreadStart is a delegate defined by the .NET Framework as shown here:
public delegate void ThreadStart( )
Thus, your entry point method must have a void return type and take no arguments. Once created, the new thread will not start running until you call its Start( ) method, which is defined by Thread. The Start( ) method has two forms. The one used here is
public void Start( )
Once started, the thread will run until the method specified by entryPoint returns. Thus, when entryPoint returns, the thread automatically stops. If you try to call Start( ) on a thread that has already been started, a ThreadStateException will be thrown. Here is an example that creates a new thread and starts it running:
// Create a thread of execution.
using System;
using System.Threading;
class MyThread {
public int Count;
string thrdName;
public MyThread(string name)
{ Count = 0;
thrdName = name;
}
// Entry point of thread.
public void Run() {
Console.WriteLine(thrdName + " starting.");
do {
Thread.Sleep(5
PART II
116
00);
Console.WriteLine("In " + thrdName + ", Count is " + Count);
Count++;
} while(Count < 10);
Console.WriteLine(thrdName + " terminating.");
}
}
class MultiThread {
static void Main() {
Console.WriteLine("Main thread starting.");
// First, construct a MyThread object.
MyThread mt = new MyThread("Child #1");
// Next, construct a thread from that object.
Thread newThrd = new Thread(mt.Run);
// Finally, start execution of the thread.
newThrd.Start();
do {
Console.Write(".");
Thread.Sleep(100);
} while (mt.Count != 10);
Console.WriteLine("Main thread ending.");
}
}
Let’s look closely at this program. MyThread defines a class that will be used to
create a second thread of execution. Inside its Run( ) method, a loop is established that counts from 0 to 9. Notice the call to Sleep( ), which is a static method defined by Thread. The Sleep( ) method causes the thread from which it is called to suspend execution for the specified period of milliseconds. When a thread suspends, another thread can run. The form used by the program is shown here:
public static void Sleep(int milliseconds)
The number of milliseconds to suspend is specified in milliseconds. If milliseconds is zero, the calling thread is suspended only to allow a waiting thread to execute.
Inside Main( ), a new Thread object is created by the following sequence of statements:
// First, construct a MyThread object.
MyThread mt = new MyThread("Child #1");
// Next, construct a thread from that
object. Thread newThrd = new Thread(mt.Run);
// Finally, start execution of the
thread. newThrd.Start();
As the comments suggest, first an object of MyThread is created. This object is then used to construct a Thread object by passing the mt.Run( ) method as the entry point. Finally, execution of the new thread is started by calling Start( ). This causes mt.Run( )
117
to begin executing in its own thread. After calling Start( ), execution of the main thread returns to Main( ), and it enters Main( )’s do loop. Both threads continue running, sharing the CPU, until their loops finish. The output produced by this program is as follows. (The precise output that you see may vary slightly because of differences in your execution environment, operating system, and task load.)
Main thread
starting. Child #1
starting.
.....In Child #1, Count is 0
.....In Child #1, Count is 1
.....In Child #1, Count is 2
.....In Child #1, Count is 3
.....In Child #1, Count is 4
.....In Child #1, Count is 5
.....In Child #1, Count is 6
.....In Child #1, Count is 7
.....In Child #1, Count is 8
.....In Child #1, Count is 9
Child #1
terminating. Main
thread ending.
Creating Multiple Threads The preceding examples have created only one child thread. However, your program can spawn as many threads as it needs. For example, the following program creates three child threads:
// Create multiple threads of execution.
using System;
using System.Threading;
class MyThread {
public int Count;
public Thread Thrd;
public MyThread(string name) {
Count = 0;
Thrd = new Thread(this.Run);
Thrd.Name = name;
Thrd.Start();
}
// Entry point of thread.
void Run() {
Console.WriteLine(Thrd.Name + " starting.");
do {
Thread.Sleep(500);
Console.WriteLine("In " + Thrd.Name +
", Count is " + Count);
Count++;
} while(Count < 10);
Console.WriteLine(Thrd.Name + " terminating.");
118
}
}
class MoreThreads {
static void Main()
{
Console.WriteLine("Main thread starting.");
// Construct three threads.
MyThread mt1 = new MyThread("Child
#1"); MyThread mt2 = new
MyThread("Child #2"); MyThread mt3
= new MyThread("Child #3");
do {
Console.Write
(".");
Thread.Sleep(
100);
} while (mt1.Count < 10 ||
mt2.Count <
10 ||
mt3.Count <
10);
Console.WriteLine("Main thread ending.");
}
}
Sample output from this program is shown next:
Main thread starting.
.Child #1 starting.
Child #2 starting.
Child #3 starting.
....In Child #1, Count is 0
In Child #2, Count is 0
In Child #3, Count is 0
.....In Child #1, Count is 1
In Child #2, Count is 1
In Child #3, Count is 1
.....In Child #1, Count is 2
In Child #2, Count is 2
In Child #3, Count is 2
.....In Child #1, Count is 3
In Child #2, Count is 3
In Child #3, Count is 3
.....In Child #1, Count is 4
In Child #2, Count is 4
In Child #3, Count is 4
.....In Child #1, Count is 5
In Child #2, Count is 5
In Child #3, Count is 5
.....In Child #1, Count is 6
In Child #2, Count is 6
PART II
119
In Child #3, Count is 6
.....In Child #1, Count is 7
In Child #2, Count is 7
In Child #3, Count is 7
.....In Child #1, Count is 8
In Child #2, Count is 8
In Child #3, Count is 8
.....In Child #1, Count is 9
Child #1 terminating.
In Child #2, Count is 9
Child #2 terminating.
In Child #3, Count is 9
Child #3
terminating. Main
thread ending.
As you can see, once started, all three child threads share the CPU. Again, because of differences among system configurations, operating systems, and other environmental factors, when you run the program, the output you see may differ slightly from that shown here.
Determining When a Thread Ends Often it is useful to know when a thread has ended. In the preceding examples, this was attempted by watching the Count variable—hardly a satisfactory or generalizable solution. Fortunately, Thread provides two means by which you can determine whether a thread has ended. First, you can interrogate the read-only IsAlive property for the thread. It is defined like this:
public bool IsAlive { get; }
IsAlive returns true if the thread upon which it is called is still running. It returns false otherwise. To try IsAlive, substitute this version of MoreThreads for the one shown in the preceding program:
// Use IsAlive to wait for threads to
end. class MoreThreads {
static void Main() {
Console.WriteLine("Main thread starting.");
// Construct three threads.
MyThread mt1 = new MyThread("Child #1");
MyThread mt2 = new MyThread("Child #2");
MyThread mt3 = new MyThread("Child #3");
do { Console.Write(".");
Thread.Sleep(100);
} while (mt1.Thrd.IsAlive && mt2.Thrd.IsAlive && mt3.Thrd.IsAlive);
Console.WriteLine("Main thread ending.");
}
}
This version produces the same output as before. The only difference is that it uses IsAlive to wait for the child threads to terminate.
Another way to wait for a thread to finish is to call Join( ). Its simplest form is shown here:
120
public void Join( )
Join( ) waits until the thread on which it is called terminates. Its name comes from the concept of the calling thread waiting until the specified thread joins it. A ThreadStateException will be thrown if the thread has not been started. Additional forms of Join( ) allow you to specify a maximum amount of time that you want to wait for the specified thread to terminate.
Here is a program that uses Join( ) to ensure that the main thread is the last to stop:
// Use
Join().
using
System;
using System.Threading;
class MyThread {
public int Count;
public Thread Thrd;
public MyThread(string name) {
Count = 0;
Thrd = new Thread(this.Run);
Thrd.Name = name;
Thrd.Start();
}
// Entry point of
thread. void Run() {
Console.WriteLine(Thrd.Name + " starting.");
do {
Thread.Sleep(500
);
Console.WriteLine("In " + Thrd.Name + ", Count is " + Count);
Count++;
} while(Count < 10);
Console.WriteLine(Thrd.Name + " terminating.");
}
}
// Use Join() to wait for
threads to end.
class JoinThreads {
static void Main() {
Console.WriteLine("Main thread starting.");
// Construct three threads.
MyThread mt1 = new MyThread("Child #1");
MyThread mt2 = new MyThread("Child #2");
MyThread mt3 = new MyThread("Child #3");
mt1.Thrd.Join();
PART II
121
Console.WriteLine("Child #1 joined.");
mt2.Thrd.Join();
Console.WriteLine("Child #2 joined.");
mt3.Thrd.Join();
Console.WriteLine("Child #3 joined.");
Console.WriteLine("Main thread ending.");
}
}
Sample output from this program is shown here. Remember when you try the program, your output may vary slightly.
Main thread starting.
Child #1 starting.
Child #2 starting.
Child #3 starting.
In Child #1, Count is 0
In Child #2, Count is 0
In Child #3, Count is 0
In Child #1, Count is 1
In Child #2, Count is 1
In Child #3, Count is 1
In Child #1, Count is 2
In Child #2, Count is 2
In Child #3, Count is 2
In Child #1, Count is 3
In Child #2, Count is 3
In Child #3, Count is 3
In Child #1, Count is 4
In Child #2, Count is 4
In Child #3, Count is 4
In Child #1, Count is 5
In Child #2, Count is 5
In Child #3, Count is 5
In Child #1, Count is 6
In Child #2, Count is 6
In Child #3, Count is 6
In Child #1, Count is 7
In Child #2, Count is 7
In Child #3, Count is 7
In Child #1, Count is 8
In Child #2, Count is 8
In Child #3, Count is 8
In Child #1, Count is 9
Child #1 terminating.
In Child #2, Count is 9
Child #2 terminating.
In Child #3, Count is 9
Child #3 terminating.
Child #1 joined.
Child #2 joined.
Child #3 joined.
Main thread ending.
As you can see, after the calls to Join( ) return, the threads have stopped executing.
122
The IsBackground Property As mentioned earlier, the .NET Framework defines two types of threads: foreground and background. The only difference between the two is that a process won’t end until all of its foreground threads have ended, but background threads are terminated automatically after all foreground threads have stopped. By default, a thread is created as a foreground thread. It can be changed to a background thread by using the IsBackground property defined by Thread, as shown here:
public bool IsBackground { get; set; }
To set a thread to background, simply assign IsBackground a true value. A value of false indicates a foreground thread.
Thread Priorities Each thread has a priority setting associated with it. A thread’s priority determines, in part, how frequently a thread gains access to the CPU. In general, low-priority threads gain access to the CPU less often than high-priority threads. As a result, within a given period of time, a low-priority thread will often receive less CPU time than a high-priority thread.
public ThreadPriority Priority{ get; set; }
ThreadPriority is an enumeration that defines the following five priority settings:
ThreadPriority.Highest ThreadPriority.AboveNormal ThreadPriority.Normal ThreadPriority.BelowNormal ThreadPriority.Lowest
The default priority setting for a thread is ThreadPriority.Normal.
Synchronization When using multiple threads, you will sometimes need to coordinate the activities of two or more of the threads. The process by which this is achieved is called synchronization. The most common reason for using synchronization is when two or more threads need access to a
PART II
123
shared resource that can be used by only one thread at a time. For example, when one thread is writing to a file, a second thread must be prevented from doing so at the same time. Another situation in which synchronization is needed is when one thread is waiting for an event that is caused by another thread. In this case, there must be some means by which the first thread is held in a suspended state until the event has occurred. Then the waiting thread must resume execution.
The key to synchronization is the concept of a lock, which controls access to a block of code within an object. When an object is locked by one thread, no other thread can gain access to the locked block of code. When the thread releases the lock, the object is available for use by another thread.
The lock feature is built into the C# language. Thus, all objects can be synchronized. Synchronization is supported by the keyword lock. Since synchronization was designed into C# from the start, it is much easier to use than you might first expect. In fact, for many programs, the synchronization of objects is almost transparent.
The general form of lock is shown here:
lock(lockObj) { // statements to be synchronized
} The following program demonstrates synchronization by controlling access to a method
called SumIt( ), which sums the elements of an integer array:
// Use lock to synchronize access to an object.
using System;
using System.Threading;
class SumArray {
int sum;
object lockOn = new object(); // a private object to lock on
public int SumIt(int[] nums) {
124
lock(lockOn) { // lock the entire method
sum = 0; // reset sum
for(int i=0; i < nums.Length; i++) {
sum += nums[i];
Console.WriteLine("Running total for " +
Thread.CurrentThread.Name +
" is " + sum);
Thread.Sleep(10); // allow task-switch
}
return sum;
}
}
}
class MyThread {
public Thread Thrd;
int[] a;
int answer;
// Create one SumArray object for all instances of MyThread.
static SumArray sa = new SumArray();
// Construct a new thread.
public MyThread(string name, int[] nums) {
a = nums;
Thrd = new Thread(this.Run);
Thrd.Name = name;
Thrd.Start(); // start the thread
}
// Begin execution of new thread.
void Run() {
Console.WriteLine(Thrd.Name + " starting.");
answer = sa.SumIt(a);
Console.WriteLine("Sum for " + Thrd.Name +
" is " + answer);
Console.WriteLine(Thrd.Name + " terminating.");
}
}
class Sync {
static void Main() {
int[] a = {1, 2, 3, 4, 5};
MyThread mt1 = new MyThread("Child #1", a);
MyThread mt2 = new MyThread("Child #2", a);
mt1.Thrd.Join();
mt2.Thrd.Join();
}
}
PART II
125
Here is sample output from the program. (The actual output you see may vary slightly.)
Child #1 starting.
Running total for Child #1 is 1
Child #2 starting.
Running total for Child #1 is 3
Running total for Child #1 is 6
Running total for Child #1 is 10
Running total for Child #1 is 15
Running total for Child #2 is 1
Sum for Child #1 is 15
Child #1 terminating.
Running total for Child #2 is 3
Running total for Child #2 is 6
Running total for Child #2 is 10
Running total for Child #2 is 15
Sum for Child #2 is 15
Child #2 terminating.
The Monitor Class and lock The C# keyword lock is really just shorthand for using the synchronization features defined by the Monitor class, which is defined in the System.Threading namespace. Monitor defines several methods that control or manage synchronization. For example, to obtain a lock on an object, call Enter( ). To release a lock, call Exit( ). These methods are shown here:
public static void Enter(object syncOb) public static void Exit(object syncOb)
Here, syncOb is the object being synchronized. If the object is not available when Enter( ) is called, the calling thread will wait until it becomes available. You will seldom use Enter( ) or Exit( ), however, because a lock block automatically provides the equivalent. For this reason, lock is the preferred method of obtaining a lock on an object when programming in C#.
One method in Monitor that you may find useful on occasion is TryEnter( ). One of its forms is shown here:
public static bool TryEnter(object syncOb)
It returns true if the calling thread obtains a lock on syncOb and false if it doesn’t. In no case does the calling thread wait. You could use this method to implement an alternative if the desired object is unavailable.
Monitor also defines these three methods: Wait( ), Pulse( ), and PulseAll( ). They are described in the next section.
Terminating a Thread It is sometimes useful to stop a thread prior to its normal conclusion. For example, a debugger may need to stop a thread that has run wild. Once a
PART II
126
thread has been terminated, it is removed from the system and cannot be restarted.
To terminate a thread prior to its normal ending point, use Thread.Abort( ). Its simplest form is shown here:
public void Abort( )
Abort( ) causes a ThreadAbortException to be thrown to the thread on which Abort( ) is called. This exception causes the thread to terminate. This exception can also be caught by your code (but is automatically rethrown in order to stop the thread). Abort( ) may not always be able to stop a thread immediately, so if it is important that a thread be stopped before your program continues, you will need to follow a call to Abort( ) with a call to Join( ). Also, in rare cases, it is possible that Abort( ) won’t be able to stop a thread. One way this could happen is if a finally block goes into an infinite loop.
The following example shows how to stop a thread by use of Abort( ):
// Stopping a thread by use of Abort().
using System;
using System.Threading;
class MyThread {
public Thread Thrd;
public MyThread(string name) {
Thrd = new Thread(this.Run);
Thrd.Name = name;
Thrd.Start();
}
// This is the entry point
for thread. void Run() {
Console.WriteLine(Thrd.Name + " starting.");
for(int i = 1; i <= 1000; i++) {
Console.Write(i + " ");
if((i%10)==0) {
Console.WriteLine();
Thread.Sleep(250);
}
}
Console.WriteLine(Thrd.Name + " exiting.");
}
}
class StopDemo {
static void Main() {
MyThread mt1 = new MyThread("My Thread");
Thread.Sleep(1000); // let child thread start executing
Console.WriteLine("Stopping thread.");
mt1.Thrd.Abort();
mt1.Thrd.Join(); // wait for thread to terminate
PART II
127
Console.WriteLine("Main thread terminating.");
}
}
The output from this program is shown here:
My Thread starting.
1 2 3 4 5 6 7 8 9 10
11 12 13 14 15 16 17 18 19 20
21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40
Stopping thread.
Main thread terminating.
NOTE Abort( ) should not be used as the normal means of stopping a thread. It is meant for specialized situations. Usually, a thread should end because its entry point method returns.
Determining a Thread’s State The state of a thread can be obtained from the ThreadState property provided by Thread. It is shown here:
public ThreadState ThreadState{ get; }
The state of the thread is returned as a value defined by the ThreadState enumeration. It defines the following values:
ThreadState.Aborted ThreadState.AbortRequested
ThreadState.Background ThreadState.Running
ThreadState.Stopped ThreadState.StopRequested
ThreadState.Suspended ThreadState.SuspendRequested
ThreadState.Unstarted ThreadState.WaitSleepJoin
All but one of these values is self-explanatory. The one that needs some explanation is ThreadState.WaitSleepJoin. A thread enters this state when it is waiting because of a call to Wait( ), Sleep( ), or Join( ).
128
What is ADO.Net?
• The data access classes for the .Net framework
• Designed for highly efficient data access
• Support for XML and disconnected record sets
ADO versus ADO.NET AD O.NET Architecture
Feature ADO ADO.NET
Primary Aim Client/server coupled
Disconnected collection of data from data server
Form of data in memory
Uses RECORDSET object (contains one table)
Uses DATASET object (contains one or more DATATABLE objects)
Disconnected access
Uses CONNECTION object and RECORDSET object with OLEDB
Uses DATASETCOMMAND object with OLEDB
Disconnected access across multi-tiers
Uses COM to marshal RECORDSET
Transfers DATASET object via XML. No data conversions required
XML capabilities
XML aware XML is the native transfer medium for the objects
Firewalls Firewalls block system-level COM marshalling
XML flows through the firewall via HTTP
Code
Coupled to the language used, various implementation
Managed code library – Uses Common Language Runtime, therefore, language agnostic
129
ADO.NET Architecture
ADO.NET Namespaces
System.Data Core namespace, defines types that represent data
System.Data.Common Types shared between managed providers
System.Data.OleDb Types that allow connection to OLE DB compliant data sources
System.Data.SqlClient Types that are optimized to connect to Microsoft® SQL Server
System.Data.SqlTypes Native data types in Microsoft® SQL Server
130
Importing the ADO.NET Namespaces Needed to build a data access application
• For OLE DB:
Imports System.Data Imports System.Data.OleDB
• For SQL Server:
Imports System.Data Imports System.Data.SQLClient Connection object
• Connects to databases.
• Two provider-specific classes
• SqlConnection
• OleDbConnection.
• Connections can be opened in two ways:
• Explicitly by calling the Open method on the connection
• Implicitly when using a DataAdapter.
• Connections handle transactions
//Example: Connection Object using System; using System.Windows.Forms; using System.Data.OleDb; namespace WindowsApplication1 { public partial class Form1 : Form { public Form1() {
131
InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { string connetionString = null; OleDbConnection cnn ; connetionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=yourdatabasename.mdb;"; cnn = new OleDbConnection(connetionString); try { cnn.Open(); MessageBox.Show ("Connection Open ! "); cnn.Close(); } catch (Exception ex) { MessageBox.Show("Can not open connection ! "); } } } } Command Object
• Information submitted to a database as a query via a Connection object
• Two provider-specific classes
• SqlCommand
• OleDbCommand
• Input and output parameters are supported, along with return values as part of the command syntax
• Results are returned in the form of streams. Accessed by:
• DataReader object
• DataSet object via a DataAdapter
132
DataReader Object
• Provides methods and properties that deliver a forward-only stream of data rows from a data source
• When a DataReader is used, parts of the ADO.NET model are cut out, providing faster and more efficient data access
• Access to data is on a per record basis.
• Forward only
• Read only
• Does support multiple recordsets
//Example : OleDbDataReader using System; using System.Windows.Forms; using System.Data.OleDb; namespace WindowsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { string connetionString = null; OleDbConnection oledbCnn ; OleDbCommand oledbCmd ; string sql = null; connetionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=Your mdb filename;"; sql = "Your SQL Statement Here like Select * from product";
133
oledbCnn = new OleDbConnection(connetionString); try { oledbCnn.Open(); oledbCmd = new OleDbCommand(sql, oledbCnn); OleDbDataReader oledbReader = oledbCmd.ExecuteReader(); while (oledbReader.Read ()) { MessageBox.Show(oledbReader.GetValue(0) + " - " + oledbReader.GetValue(1) + " - " + oledbReader.GetValue(2)); } oledbReader.Close(); oledbCmd.Dispose(); oledbCnn.Close(); } catch (Exception ex) { MessageBox.Show("Can not open connection ! "); } } } } DataAdapter Object
• Provides a set of methods and properties to retrieve and save data between a DataSet and its source data store
• Allows the use of stored procedures
• Connects to the database to fill the DataSet and also update the database
• Pipeline between DataSets and data sources
• Geared towards functionality rather than speed
• Disconnected by design
• Supports select, insert, delete, update commands and methods
Using the DataAdapter SQLDataAdapter sqlDA = new SqlDataAdapter(); sqlDA.SelectCommand = new SqlCommand ("select * from authors“,
134
sqlConnection); DataSet sqlDS = new DataSet("authorsTable"); sqlDA.Fill(sqlDS, "authorsTable"); DataSet Object
• Replaces the ADO Recordset
• Represents a cache of data that contains tables, columns, relationships, and constraints, just like a database
• Regardless of where the source data comes from, data can all be placed into DataSet objects
• Tracks changes that are made to the data it holds before updating the source data
• DataSet are also fully XML-featured
• Works with all current models of data storage: flat, relational, and hierarchical
Creating DataSets • Setup SqlConnection
• Setup a SqlDataAdapter
• Create a DataSet
• Call the .Fill() method on the DA
// Example: OleDbDataAdapter & DataSet using System; using System.Data; using System.Data.OleDb; using System.Windows.Forms; namespace WindowsApplication1 { public partial class Form1 : Form { public Form1() {
135
InitializeComponent(); } private void button1_Click(object sender, EventArgs e) { string connetionString = null; OleDbConnection connection ; OleDbDataAdapter oledbAdapter ; DataSet ds = new DataSet(); string sql = null; int i = 0; connetionString = "Provider=Microsoft.Jet.OLEDB.4.0;Data Source=Your mdb filename;"; sql = "Your SQL Statement Here"; connection = new OleDbConnection(connetionString); try { connection.Open(); oledbAdapter = new OleDbDataAdapter(sql, connection); oledbAdapter.Fill(ds); oledbAdapter.Dispose(); connection.Close(); for (i = 0; i <= ds.Tables[0].Rows.Count - 1; i++) { MessageBox.Show (ds.Tables[0].Rows[i].ItemArray[0] + " -- " + ds.Tables[0].Rows[i].ItemArray[1]); } } catch (Exception ex) { MessageBox.Show("Cannot open connection ! "); } } } } DataTables
136
• A DataSet contains one or more DataTables.
• Fields are held within the DataTable.
• And in DataRows, DataColumns.
DataView Object
• Provides methods and properties that enable UI objects such as a DataGrid to
bind to a DataSet
• A view of the data contained in the DataSet
• Only used in conjunction with a DataSet