Saturday, 26 May 2012

ADO.NET Concepts With example

COMMAND OBJECT
 Command object is the biggest object in ADO.NET
 It is the only object which can perform actions with database
 It  can be created directly using Command class or can be created using Connection.Create
command (factory classes also contain creation of Command).
Ex:  SqlConnection sqlCon = new SqlConnection("......");
   SqlCommand sqlCmd = sqlCon.CreateCommand();
 Commands that we run depend upon the kind of query that we want to execute with database.
All databases support two types of queries.
i. Action Queries
ii. Non-action Queries
Action queries are those which change the state of database and which don‟t return any query
results(though they return the number of records affected). Ex: Insert, Delete and Update
statements
Non-action queries are those which don‟t affect the database but return the results to the
user. Ex: Select statement
Method of execution of queries:



Command object provides the following methods to execute queries:
1. ExecuteNonQuery()
2. ExecuteReader()
3. ExecuteScalar()
4. ExecuteXMLReader()
1. ExecuteNonQuery():  This method is used for executing the queries which perform some action
and change the state of the database. This method is used to execute insert, update and delete
statements.
Ex: Delete * from  tmpjobs;
        Insert into jobs values(„abc‟, 100, 120);
2. ExecuteReader(): This method can run any select statement.


Ex: Select ename from emp;   //which returns multiple values
       Select count(*) from emp; //which returns a single value
       Select E.Empno, E.Ename, D.Deptno, D.Dname  from emp E, Dept D where 
      E.Deptno=D.Deptno  //which returns values from multiple tables
3. ExecuteScalar(): This method is used for select statements which return only single values.
Ex: Select count(*) from emp;
4. ExecuteXMLReader(): This method is applicable only for SqlClient namespace. It is used for
queries which return xml data. But Oledb managed provider does not support this method.
Ex: Select * from jobs FOR XML AUTO





Note: ExecuteNonQuery(),  which is included from ADO.NET Version 1.0/1.1 is used for synchronous
execution of queries, whereas  BeginExecuteQuery() and EndExecuteQuery() methods which were introduced
in ADO.NET Version 2.0 are  used for asynchronous execution of queries.
All the methods of Command object can run all the actions. But we have to use this object carefully
depending upon our requirements, otherwise performance will go down.
Command Object Demo:
This program demonstrates the use of Command object for a delete statement. In this program when
the linklabel is clicked, the corresponding record in the job table of pubs database will be deleted
depending on job_id value given in textbox.
The following connection string is declared in app.config file:
<configuration>
  <appSettings>

    <add key="sqlCnStr" value="data source=mytoy; user id=sa; database=pubs"/>
  </appSettings>
</configuration>


Design:
The form is designed with just a textbox and a linklabel as shown below


Code:
    public partial class cmdobj : Form
    {
  SqlConnection cn = new SqlConnection(ConfigurationSettings.AppSettings["sqlCnStr"]);
                 SqlCommand cmd;
                 string stmt;
                 public cmdobj()
                 {
                       InitializeComponent();
                  }
                   private void cmdobj_Load(object sender, EventArgs e)
                  {
                         cn.Open();           
                   }
                   private void lnklblDelete_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
                  {
                 stmt = "delete from jobs where job_id=" + txtJobId.Text;
             cmd = new SqlCommand();
             cmd.CommandText = stmt;
             cmd.CommandType = CommandType.Text;
             cmd.Connection = cn;
             cmd.CommandTimeout = 20;
             int i = cmd.ExecuteNonQuery();
            if (i > 0)
                     MessageBox.Show("Record deleted successfully");
            else MessageBox.Show("Records not found");
            MessageBox.Show("No. of records deleted: "+i);
        
            }       
      
       }
When the job id is given in the textbox and delete linklabel is clicked, it should delete the
corresponding records from the database. For this purpose ExecuteNonQuery() method of command
object is used for executing the delete statement. If the records are successfully deleted it returns the
number of records deleted, otherwise returns zero value.


Batch SQL:
We can write more than one SQL statement in a single string and execute them as a group(internally
execution takes place one after the other) at the same time. This is called batch SQL.
The statements are separated by a semi-colon and written in the same string.
Ex: “select ename from emp; select * from jobs; select * from dept”
Batch SQL is very effective and saves time because it is not necessary to wait for the execution of
first  statement, in order to run the next statement. But there is also a great drawback in this approach,
which relates to the SQL injection attacks.
SQL Injection attacks:
If even a single statement which is harmful to the database is provided in the batch, it results in
stalling of the whole database. This is a great disadvantage of command objects.
For example, take the same demo written earlier. In that program there is a provision for the user to
enter the job_id in the given text box.
stmt = "delete from jobs where job_id=" + txtJobId.Text;
cmd = new SqlCommand();
cmd.CommandText = stmt;
cmd.CommandType = CommandType.Text;
This works fine as long as the user enters the correct job_id. But the drawback here is that the user can
include another statement in the same text box after providing the required input. Suppose the job_id
of the record to be deleted is 15. So the user is expected to enter 15 in the textbox, but instead if he
enters something like this:
15; drop table emp
So the total string becomes:
“delete from jobs where job_id=15; drop table emp”
As the command object is capable of performing batch SQL operations, it tries to execute both
statements on the database. This results in dropping of the complete emp table which is not desirable.
Some validations, permissions and stored procedures can be used to overcome these problems.
Moreover SQL2005 and 2008 also provide some utilities to avoid these scenarios.
Making little changes in the design and code of the application can also help in avoiding SQL
injection attacks to some extent.
Demo2: Adding and updating records in a table from front-end
In this program the operations add and update can be performed on the jobs table from the front-end
by using the command object. Three different ways of adding the records are shown here. The form
design is as follows:


The four text boxes correspond to the four columns in the jobs table. Since  job_id is an identity
column, there is no need to enter any values for that column while adding records. But while updating
records it is necessary to provide job_id value to identify the correct record.
Code:
string stat;
SqlConnection cn;
SqlCommand cmdobj;
//to open connection and execute the command statement
public int performAction(string st)
{
    cn = new SqlConnection("data source=mytoy; user id=sa; database=pubs");
    cn.Open();
cmdobj = new SqlCommand(stat, cn);
    int i = cmdobj.ExecuteNonQuery();
    return i;
}
//To update records
private void btnUpdate_Click(object sender, EventArgs e)
{
    stat = "update jobs set job_desc='" + txtDesc.Text + "', min_lvl=" +  txtMinLvl.Text + ", max_lvl=" + 
                txtMaxLvl.Text + " where job_id=" + txtJobId.Text;
    int a = performAction(stat);
    MessageBox.Show("Records updated:" + a.ToString());
}
//First Method of adding records
private void btnAdd_Click(object sender, EventArgs e)
{
    stat = "insert into jobs values('" + txtDesc.Text + "'," + 
                     txtMinLvl.Text + "," + txtMaxLvl.Text + ")";
   int a = performAction(stat);
    if (a > 0)

     MessageBox.Show("Record added sucessfully");
    else
        MessageBox.Show("Unsuccessful operation");
}
//Second Method of adding records
private void btnAdd_Click(object sender, EventArgs e)
{

    stat=string.Format("Insert into jobs values('{0}',{1},{2})", txtDesc.Text, txtMinLvl.Text, txtMaxLvl.Text);
    int a = performAction(stat);
    if (a > 0)
       MessageBox.Show("Record added sucessfully");
    else
       MessageBox.Show("Unsuccessful operation");
}
//Third Method of adding records
private void btnAdd_Click(object sender, EventArgs e)
{
    cn = new SqlConnection("data source=mytoy; user id=sa; database=pubs");
    cn.Open();
stat = "insert into jobs values(@a,@b,@c)";
    cmdobj = new SqlCommand(stat, cn);
    cmdobj.Parameters.AddWithValue("@a", txtDesc.Text);
    cmdobj.Parameters.AddWithValue("@b", txtMinLvl.Text);
    cmdobj.Parameters.AddWithValue("@c", txtMaxLvl.Text);
    int a=cmdobj.ExecuteNonQuery();
          
    if (a > 0)
       MessageBox.Show("Record added sucessfully");
    else
       MessageBox.Show("Unsuccessful operation");
}

In the above form all front-end objectives can be viewed with respect to databasees. A front-end
should provide design and that design should be interactive with databases.
Three ways of adding records to the database are shown in this program. In the first one, a command
string is provided to the command object with the textbox values directly embedded in it. This string
is executed by using ExecuteNonQuery method.
In the second approach also a command string is provided,but the values are  inserted into the string
as arguments using string.Format method.
In the third approach, command parameters are used to supply the textbox values to the command
string which is executed by using the ExecuteNonQuery() method.

DataReader
In ADO.NET there are only two objects which can store data. One is DataSet while the other is
DataReader.
Features of DataReader:
 DataReader is a forward only accessible object
 It is a read-only object
 It provides sequential access for rows and can be extended for sequential column access also.
How DataReader Works:

The select statement from the command object is processed and a ResultSet with the required data is
created in the form of rows and columns at the database server. The address of this ResultSet is
provided to the DataReader.
cmd=new SqlCommand(“select * from emp”, cn); 
dr=cmd.ExecuteReader();   //provides the address of the ResultSet to the DataReader(dr)
When the read() method of the DataReader is used it returns true if records are present and false if
records are not present. At the same time if records are present, a similar structure like the ResultSet is
created at the client side and the first record is fetched into it. The DataReader reads the record from
this structure.
dr[n] is an indexer to access its created structure.
Ex:  dr[0] provides the first column of the retrieved row
        dr[1] provides the second column of the retrieved row

A pointer exists on ResultSet to identify the record being read. When the read() method is again called
from the DataReader, the pointer moves to the next record if present. The structure at the DataReader
now gets overwritten by this record.
Drawbacks:
 It is a forward-only and read-only object
 DataReader is a connection-oriented object, which means that the access to data is possible
only as long as the connection exists.
 DataReader works only on databases, but not on other data sources like XML files.
Note: It is always necessary to close the DataReader object after the required data is read.
Inspite of all drawbacks, DataReader usage is in its performance. To access data, DataReader is the
fastest object in entire .NET(not only in ADO.NET).
DataReader capabilities are enhanced in .NET 2.0. The new features are:
 It can communicate with other objects like DataSet
             DataReader DataSet(DataTable)
             DataTable  DataReader
 The biggest enhancement is MARS(Multiple Active ResultSets)
(In earlier versions it was SAR(Single Active ResultSet)
Multiple Active ResultSets(MARS):

Multiple ResultSets are created when Batch SQL is provided. i.e. for each individual statement in the
batch, a different ResultSet is created.  But normally only one ResultSet is active at a time. If the next
ResultSet has to be active, then the previous one should become inactive. This is called Single Active
ResultSet eventhough multiple ResultSets are present.  But with MARS, more than one ResultSet can
be active at a time. This property can be specified in the connection string by giving true or false .

SqlConnection cn=new SqlConnection(“data source=...... ; MultipleActiveResultSets=true; ...”);
SingleActiveResultSet is the default and so we need to specify MARS explicitly in connection string.
Using MARS eliminates the need to open and close connection for each ResultSet and hence
improves the performance. But this is not the case at all times. The performance is improved only
when multiple commands have to be executed using a single connection. On the other hand it is not
worth using for very few commands and it even results in some overhead in certain situations. So
MARS should be used only when it is really needed.
Note:
We can use ExecuteReader() method for action queries also(insert, update, delete). When there is a
combination of select and any action query in Batch SQL , we have to use ExecuteReader, which gets
address of the ResultSet of select query and also does the action of the action query.
DataReader Demo:
This program shows how a DataReader can be used to read the data fetched from the database.  A
job_id is provided in the relevant textbox and searched for the record with that job_id. If matching
record is present in the database, it is fetched into the datareader, which reads the column values and
fills the textboxes provided for the purpose.

SqlConnection cn = new SqlConnection("data source=mytoy; user id=sa; 
                                      database=pubs");
SqlCommand cmd;
SqlDataReader dr;
private void btnSearch_Click(object sender, EventArgs e)
{
     string stat = "select * from jobs where job_id=" + txtJobId.Text;
     if (cn.State.ToString() == "Closed")
        cn.Open();
     cmd = new SqlCommand(stat, cn);
     dr = cmd.ExecuteReader();
if (dr.Read())
     {

txtDesc.Text = dr[1].ToString();
txtMinVal.Text = dr[2].ToString();
txtMaxVal.Text = dr[3].ToString();
dr.Close();
      }
else
         MessageBox.Show("Record not found");
          
}
More DataReader Methods and Properties:
1.dr.GetString(0):  This statement is used to retrieve the data in the required format. Here the
requirement is to retrieve the first column value of the DataReader in the form of a string. Normally
for this purpose, the below method is adopted:
textBox1.Text=dr[0].ToString();
Here the data is first retrieved and then converted into string format. But if dr.GetString(0) is used, the
data is directly retrieved in the form of string, which aids in  better performance.
2.dr.GetInt32(1): This works similar to the above method, but fetches data in the form of an integer.
   Similarly there are methods like GetDouble, GetByte,GetDateTime etc. for different datatypes.
3. HasRows Property: It checks whether records are present or not and returns true or false
accordingly. This property provides better performance than Read() because Read method checks the
presence of the records and also reads the records. Since HasRows property does not read the records
it is faster.
4.IsClosed: It returns true if the DataReader is closed, otherwise returns false.
If cmd.ExecuteReader(CommandBehaviour.CloseConnection) is used then connection gets closed
when DataReader is closed. In such cases, the state of the connection  can be checked by using
IsClosed property.
5.IsDBNull: This property is used to check if a DataRow is null. Returns true if it is null, otherwise
returns false.
Ex: if(dr.IsDBNull[0]) checks whether the first row is null or not.
DataReader Demo2:
This wpf program demonstrates the retrieval of columnwise data from database using DataReader.
This kind of sequential access is required  when there are columns with more volume of data like
structures, MS-Word documents, Excel etc.
Here a listbox, image and a button are taken in the window. The button code is written so that the
image names in the table are shown in the list box. When the required image name is chosen in the
listbox, the actual image is shown beside it.

SqlConnection cn = new SqlConnection("data source=mytoy; user id=sa;
                       database=pubs");
SqlCommand cmd;
SqlDataReader dr;
private void btnViewPubs_Click(object sender, EventArgs e)
{
    cmd = new SqlCommand("select pub_id, logo from pub_info", cn);
    FileStream stream;
    BinaryWriter writer;
    int bufferSize = 100;
    byte[] outByte = new byte[buffersize];
    long retval;
    long startIndex = 0;
    string pubId = "";
    cn.Open();
SqlDataReader reader = 
                    cmd.ExecuteReader(CommandBehavior.SequentialAccess);
    while (reader.Read())
    {
        pubId = reader.GetString(0);
string fname = "logo" + pubId + ".bmp";
        stream = new FileStream(fname, FileMode.OpenOrCreate,
                                FileAccess.Write);
        writer = new BinaryWriter(stream);
        startIndex = 0;
retval = reader.GetBytes(1, startIndex, outByte, 0, bufferSize);
while (retval == bufferSize)
        {
            writer.Write(outByte);
writer.Flush();
startIndex += bufferSize;
retval = reader.GetBytes(1,startIndex,outByte,0,bufferSize);
        }
writer.Write(outByte, 0, (int)retval - 1);
        writer.Flush();
writer.Close();
        stream.Close();
        listBox1.Items.Add(fname);

}
reader.Close();
cn.Close();
}
private void listBox1_SelectionChanged(object sender,
                                    SelectionChangedEventArgs e)
{
    BitmapImage bi = new BitmapImage();
    bi.BeginInit();
bi.UriSource=new 
Uri(Environment.CurrentDirectory+@"\"+listBox1.SelectedItem.ToString());
    bi.EndInit();
image1.Source=bi;
}
The namespace System.IO is needed for file stream operations. Bitmap images are accessed
sequentially by using the ExecuteReader method of the command object. Finally the filenames from
the table are displayed in the listbox. When image name is selected in the listbox, it displays the
image in the image placeholder.
The result will be as shown in this screen shot:

Stored Procedures:
Different methods of writing programs in back-end are:
 Blocks (T-SQL for SQL Server, PL/SQL for Oracle) - Blocks compile everytime when called.
They are not stored
 Procedures - Procedures get compiled and stored as part of database objects. So they are
faster in execution. They need to be explicitly called using an execute statement
 Functions -  They also get compiled and stored like procedures. They can be called with a
select statement.
 Triggers – They are executed automatically(implicitly called)

Why use Stored Procedures?
Stored Procedures are used because of the following features they offer:
 Modular programming
 Distribution of work
 Database security
 Faster execution
 Network traffic reduction
 Flexibility
ADO.NET uses Command object to invoke procedures.  SQL 2005/.NET 2.0 provided a new feature
of writing procedures in C#.NET and storing them in SQL Server. This option is provided as a
separate project option.  From Visual Studio we can open SQL Server by following the below path:
FileNew Project  Database SQL Server
Apart from this, VS.NET provides options like Server Explorer, T-SQL Editor where we can write,
compile, execute and store procedures at back-end.
Procedures:
A typical procedure looks like the following one:
CREATE PROCEDURE dbo.AddJob
(
@jobdesc varchar(50),
@minlvl int,
@maxlvl int
)
AS
Insert into jobs values(@jobdesc, @minlvl, @maxlvl)
RETURN
Every parameter in a procedure should be preceded by a special character @ to differentiate from the
column names of the table. By default all the declared parameters are input parameters. If the
procedure has to return values, then the output parameters must be explicitly specified by using
OUTPUT keyword for SQL Server and OUT for Oracle.

Calling procedures from client-side:
This is the code for adding records into the jobs table. The three column values are entered into the
text boxes(no need to add values to the first column because it is an identity column) and at the click
of a button the whole record should be entered into the table. This is done by using the stored
procedure shown earlier. To call that procedure, the button click code should be written as follows:
cmd = new SqlCommand("AddJob", cn);
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Parameters.AddWithValue("@jobdesc", txtJobDesc.Text);
            cmd.Parameters.AddWithValue("@minlvl", txtMinLvl.Text);
            cmd.Parameters.AddWithValue("@maxlvl", txtMaxLvl.Text);
            cmd.ExecuteNonQuery();
MessageBox.Show("Record Added");
Here the textbox values are assigned to the parameters of the procedure by using
Parameters.AddWithValue() method using command object. When the command is executed, it
executes the procedure at the database server and records are added into the table.
Note: If OleDb is used then the parameters should be in same order as  the columns in the table, but
for SQL Server this is not required.
The command types that are supported by ADO.NET are :
i. Text: This is default. It can be any query including the table name.
ii. StoredProcedure: To call a procedure at the back-end
iii. TableDirect: This is mainly for OleDb, where the table name has to be specified in place
of command text.
Procedures can be written to return query results also(i.e. select statement results). To execute the
procedures that return query results, ExecuteReader should be used from the command object instead
of ExecuteNonQuery as shown below:
   cmd = new SqlCommand("jobsinfo", cn);
            cmd.CommandType = CommandType.StoredProcedure;
            SqlDataReader dr = cmd.ExecuteReader();
But it is not preferred to write such procedures because all databases do not support them. But for
returning values from the procedures,  nearly all back-ends support directional parameters-IN, OUT,
INOUT.
i. IN Parameter: It is read-only in procedure i.e.the procedure can only read the value from
it and but cannot return value to it. The direction of this parameter is from the calling
program (client) to called program(server).
ii. OUT Parameter: It is write-only which means that the procedure can only return values to
it, but cannot read any values from it. Direction is from server to client
iii. INOUT Parameter: It can be used for both Read and Write. It is bi-directional i.e. from
server to  client as well as from client to server.
Demo-Stored Procedures:
This program demonstrates the use of OUTPUT direction parameter and also introduces the SQL
provider specific parameter class for working with stored procedures.

Here when job id is provided and searched, the corresponding job description should be displayed in
the second text box. For this purpose a stored procedure is used which takes job id  as the input
parameter and then gives its job description as the output parameter.
First the stored procedure is created as follows:
CREATE PROCEDURE dbo.GetJobInfo
(
@jobId int,
@jobDesc varchar(50)OUTPUT
)
AS
    select @jobDesc=job_desc from jobs where job_id=@jobId
  
RETURN
When the Search linklabel is clicked the stored procedure has to be called. So the code for calling this
procedure is written in the click event of that link label. The code is as follows:
SqlConnection cn = new SqlConnection("data source=mytoy; user id=sa; 
                                      database=pubs");
SqlCommand cmd;
private void llblSearch_LinkClicked(object sender,
                                        LinkLabelLinkClickedEventArgs e)
{
    SqlParameter pJobId, pJobDesc;
    cn.Open();
cmd = new SqlCommand("GetJobInfo", cn);
    cmd.CommandType = CommandType.StoredProcedure;
    pJobId = new SqlParameter("@jobId", SqlDbType.Int);
    pJobId.Value = int.Parse(txtJobId.Text);
    cmd.Parameters.Add(pJobId);
pJobDesc = new SqlParameter("@jobDesc", SqlDbType.VarChar, 50);
    pJobDesc.Direction = ParameterDirection.Output;
    cmd.Parameters.Add(pJobDesc);
cmd.ExecuteNonQuery();
MessageBox.Show("Procedure Executed");
    txtJobDesc.Text = cmd.Parameters["@jobDesc"].Value.ToString();
}
In this demo SqlParameter is used, which is another provider specific class used to prepare
parameters. The created parameters can be added in command object parameters collection.

In addition to the above, parameters can also be prepared as shown below:
(i) cmd.Parameters.Add("@jobId", SqlDbType.VarChar, 50);
(ii) cmd.Parameters.Add(new SqlParameter("@jobId",
                               SqlDbType.VarChar,50));
Note: For single valued queries ExecuteScalar() should be used, otherwise it takes the first value by
default from the retrieved values. ExecuteScalar() provides better performance because of its single
return value.



















No comments:

Post a Comment

Note: only a member of this blog may post a comment.