Introduction
This is a typical data access layer written in C# which uses ADO.NET disconnected architecture. It uses SqlDataAdapter
to update the table in database from the DataTable
using stored procedure.
If we write a query in SelectCommand
of SqlDataAdapter
, it automatically generates the required InsertCommand
, UpdateCommand
and DeleteCommand
in a simple scenario to update the database but if we pass name of Stored Procedure in SelectCommand
, then it won't be able to generate these other commands.
There is another way of doing it by table-valued parameter where you can pass the datatable
directly to stored procedure but SQL SERVER 2005 and previous version does not support it, also that requires creating User Type and enabling CLR in SQL SERVER.
I have written Stored Procedure and the way to use it in SqlDataAdapter
. SqlDataAdapter
will require only name of the stored procedure and the DataTable
which needs to be updated.
Background
I am migrating some old VB 6 applications to .NET which have used inline SQL queries. In the migrated version, we need to use stored procedure and disconnected architecture, as our SQL version does not support table-valued parameter, we are using the explained approach.
Using the Code
SQL CODE
Create one table in SQL named Customer
with the following columns:
CREATE TABLE Customer(
[ID] [int] IDENTITY(1,1) Primary Key,
[Name] [varchar](20) NULL,
[City] [varchar](20) NULL,
[DOB] [date] NULL,
[Married] [bit] NULL,
[Mobile] [int] NULL)
Create a Stored Procedure to Get the Data from Customer
Table:
Create Procedure uspGetCustomer @ID int
as
IF @ID < 1
SELECT * FROM Customer
Else
SELECT * FROM Customer WHERE ID = @ID
Create another Stored Procedure to update the Customer
Table.
Name of the parameter should be the same as column name of the table with prefix as @
.
There will be one extra parameter named @RowState
with int
type to check the passed row need to be deleted, updated or inserted.
For creating this stored procedure, you can extensively use the query generated by SQL server (Right click on table in Object Explorer -> Script Table as -> use CREATE TO
, UPDATE TO
, INSERT TO
, DELETE TO
).
Create Procedure uspUpdateCustomer @ID int ,
@Name varchar (20) ,
@City varchar (20) ,
@DOB date ,
@Married bit ,
@Mobile int ,
@RowState int
as
IF @RowState = 4
INSERT INTO Customer
([Name]
,[City]
,[DOB]
,[Married]
,[Mobile])
VALUES
(@Name
,@City
,@DOB
,@Married
,@Mobile)
IF @RowState is null and @ID is not null
DELETE FROM Customer
WHERE ID = @ID
IF @RowState = 16 and @ID is not null
UPDATE Customer
SET [Name] = @Name
,[City] = @City
,[DOB] = @DOB
,[Married] = @Married
,[Mobile] = @Mobile
WHERE ID = @ID
C# CODE
public
function of data access layer which could be accessed to get or update data. This function will need the name of the stored procedure created above. This functions could be replicated for any table.
public DataTable GetCustomer()
{
command = new SqlCommand("uspGetCustomer", connection);
command.Parameters.AddWithValue("@ID", -1);
return GetDetails();
}
public void UpdateCustomer(DataTable dtTable)
{
command = new SqlCommand("uspUpdateCustomer", connection);
UpdateDetails(dtTable);
}
Private
functions GetDetails
and UpdateDetails
. These function won't need replication for each table if you are updating multiple tables.
GetDetails
function will use Fill
functions of SqlDataAdapter
to fill the DataTable
(not much explanation required).
UpdateDetails
function will first use GetChanges
to get the updated, inserted or deleted rows so that we pass only those rows which need to changed in the database
- Add one extra column
RowState
to dtChanges
to pass the RowState
of the changed row.
For
loop will create SqlParameter
using the name
of columns in datatable
and add it in the SqlCommand
.
- Add common command to
SqlDataAdapter
for each INSERT
, UPDATE
, DELETE
and its constructor.
- Get Inserted, updated and deleted rows and
For
loop will fill the RowState
column for each row for added and modified rows. As we cannot call RowState
on deleted row, no need to set RowState
it will become null
for each deleted row.
- Next, call
Update
function of SqlDataAdapter
to update the database.
private DataTable GetDetails()
{
command.CommandType = CommandType.StoredProcedure;
adapter = new SqlDataAdapter(command);
DataTable dtTable = new DataTable();
try
{
adapter.Fill(dtTable);
}
catch (InvalidOperationException ioEx)
{
}
catch (Exception ex)
{
}
return dtTable;
}
private void UpdateDetails(DataTable dtTable)
{
SqlParameter parameter;
command.CommandType = CommandType.StoredProcedure;
DataTable dtChanges = dtTable.GetChanges();
if (dtChanges == null)
return;
dtChanges.Columns.Add("RowState", typeof(int));
for (int i = 0; i < dtChanges.Columns.Count; i++)
{
parameter = new SqlParameter();
parameter.ParameterName = "@" + dtChanges.Rows[i].Field<string>("name");
parameter.SourceColumn = dtChanges.Rows[i].Field<string>("name");
command.Parameters.Add(parameter);
}
adapter = new SqlDataAdapter(command);
adapter.InsertCommand = command;
adapter.UpdateCommand = command;
adapter.DeleteCommand = command;
DataTable dtDeletedRows = dtChanges.GetChanges(DataRowState.Deleted);
DataTable dtModifiedRows = dtChanges.GetChanges(DataRowState.Modified);
DataTable dtAddedRows = dtChanges.GetChanges(DataRowState.Added);
if (dtModifiedRows != null)
{
for (int i = 0; i < dtModifiedRows.Rows.Count; i++)
{
dtModifiedRows.Rows[i]["RowState"] = 16;
}
}
if (dtAddedRows != null)
{
for (int i = 0; i < dtAddedRows.Rows.Count; i++)
{
dtAddedRows.Rows[i]["RowState"] = 4;
}
}
try
{
if (dtAddedRows != null)
{
adapter.Update(dtAddedRows);
}
if (dtDeletedRows != null)
{
adapter.Update(dtDeletedRows);
}
if (dtModifiedRows != null)
{
adapter.Update(dtModifiedRows);
}
}
catch (Exception exception)
{
}
}
The complete Data Access Layer using the above function will look like this. ConnString
is the name of connection string in configuration file.
public class DataAccessLayer
{
SqlConnection connection;
SqlCommand command;
SqlDataAdapter adapter;
public DataAccessLayer()
{
ConnectionStringSettingsCollection settings = ConfigurationManager.ConnectionStrings;
string connectionString = settings["ConnString"].ConnectionString;
connection = new SqlConnection(connectionString);
}
public DataTable GetCustomer(){...}
public void UpdateCustomer(DataTable dtTable){...}
private DataTable GetDetails(){...}
private void UpdateDetails(DataTable dtTable){...}
}
History
- Initial version
- Updated
UpdateDetails
method to handle deleted rows, no exception handling required now