The Beginning....
In this article, I describe the following situation:
- The data access layer holds a generic method that communicates with Microsoft SQL Server
- Data to and from the database goes via stored procedures
- In the business layer, the data is collected and if needed, a
sqlparameter
(s) for a stored procedure is added to a sqlparameter
list - The generic method can detect in the config file what the first connectionstring is when none is provided in the call
- Automatic check on
NULL
values in sqlparameter
s
What I see mostly is that people create an SQL command in the onclick
event of some button and do command.parameters.Add(parameternam, some value);
. Example from MSDN:
private static void UpdateDemographics(Int32 customerID,
string demoXml, string connectionString)
{
string commandText = "UPDATE Sales.Store SET Demographics = @demographics "
+ "WHERE CustomerID = @ID;";
using (SqlConnection connection = new SqlConnection(connectionString))
{
SqlCommand command = new SqlCommand(commandText, connection);
command.Parameters.Add("@ID", SqlDbType.Int);
command.Parameters["@ID"].Value = customerID;
command.Parameters.AddWithValue("@demographics", demoXml);
try
{
connection.Open();
Int32 rowsAffected = command.ExecuteNonQuery();
Console.WriteLine("RowsAffected: {0}", rowsAffected);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
}
In this situation, all the needed data and the stored procedure name and the connectionname are send to the generic method. By using a list of sqlparameter
s, the command.Parameters.AddRange(SqlParameter[])
is used. This is where my case comes to light.
The Disadvantage of the sqlparameter Array
The Array
By using SqlParameter[]
, you have to define it with a value that holds the exact number of parameters for the stored procedure, like:
SqlParameter[] myParms = new SqlParameter[10]();
myparms[0] = new SqlParameter("MyParmName","@myParmValue");
...
But what if your stored procedure changes due to rework after some time your project is running. Well, you change the sqlparameter
s. The changes are there that you forget about your array definition of 10..... Luckily the List<>
function in .NET gives the opportunity to be transformed to an array on the fly with the ToArray()
method. So when doing the command.Parameters.AddRange(ListOfParameter)
, you use the flexibility of the List
function so you do not have to think about defined arrays.
The NULL Value
The other disadvantage of sqlparameter
s is when the value is null
. The parameter will not be sent in the request to the database so the stored procedure will be telling you that it is missing a parameter. By setting the value to DBNULL.Value
, the parameter is received in the database.
In Code Examples
The business layer holds the method that sets the properties of an object to a list of sqlparameter
s.
using System.Collections;
public class Store
{
public static List<sqlparameter> SetSqlParameters(SomeObject myObject)
{
List<sqlparameter> parms = new List<sqlparameter>();
parms.Add(new SqlParameter("@City", myObject.City));
parms.Add(new SqlParameter("@Region", myObject.Region));
parms.Add(new SqlParameter("@AreaCode", myObject.AreaCode));
parms.Add(new SqlParameter("@DmaCode", myObject.DmaCode));
parms.Add(new SqlParameter("@CountryCode", myObject.CountryCode));
parms.Add(new SqlParameter("@CountryName", myObject.CountryName));
parms.Add(new SqlParameter("@ContinentCode", myObject.ContinentCode));
parms.Add(new SqlParameter("@Lattitude", myObject.Lattitude));
parms.Add(new SqlParameter("@Longitude", myObject.Longitude));
parms.Add(new SqlParameter("@RegionCode", myObject.RegionCode));
parms.Add(new SqlParameter("@RegionName", myObject.RegionName));
parms.Add(new SqlParameter("@CurrencyCode", myObject.CurrencyCode));
parms.Add(new SqlParameter("@CurrencySymbol", myObject.CurrencySymbol));
parms.Add(new SqlParameter
("@CurrencyConverter", myObject.CurrencyConverter));
parms.Add(new SqlParameter
("@ReceivedResponse", myObject.ReceivedResponse));
return parms;
}
}
Based on the values in the SomeObject
, the list of SqlParameters
can be set. Remember that any value can be NULL
!
Now we continue with the class that will reside in the DataAccess
layer. The methods in this class are set up around the SqlCommand
and SqlConnection
objects. The purpose is to have a generic method that can be used anywhere in project(s).
Don't forget to include a reference to System.configuration
in the DataAccesslayer
project, otherwise you cannot find connectionstrings
in the config file.
using System;
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Configuration;
using System.Data;
using System.Collections;
public class SQLCommands
{
private static String DefaultConnectionName()
{
return System.Web.Configuration.WebConfigurationManager.
ConnectionStrings[0].Name;
}
#region OpenSqlCommand
public static SqlCommand OpenCommand(String StoredProcedure,
List<SqlParameter> sqlParameters, String ConnectionName)
{
ConnectionStringSettings settings = ConfigurationManager.
ConnectionStrings[ConnectionName ?? DefaultConnectionName()];
if (settings == null) throw new Exception
("No connectionstring found");
SqlCommand cmd = new SqlCommand(StoredProcedure,
new SqlConnection(settings.ConnectionString));
cmd.CommandType = CommandType.StoredProcedure;
if (sqlParameters != null)
{
CheckParameters(sqlParameters);
cmd.Parameters.AddRange(sqlParameters.ToArray());
}
cmd.CommandTimeout = 60;
cmd.Connection.Open();
return cmd;
}
private static void CheckParameters(List<SqlParameter> sqlParameters)
{
foreach (SqlParameter parm in sqlParameters)
{
if (parm.Value == null)
parm.Value = DBNull.Value;
}
}
#endregion OpenSqlCommand
#region CloseSqlCommand
public static void CloseCommand(SqlCommand sqlCommand)
{
if (sqlCommand != null &&
sqlCommand.Connection.State == ConnectionState.Open)
sqlCommand.Connection.Close();
}
#endregion CloseSqlCommand
}
What we do in the above method OpenCommand
is a SqlConnection
used by a SqlCommand
that:
- checks for web.config or app.config
connectionstringName
if not added - checks for
null
values in the SqlParameter
list and set then to DBNULL.Value
if needed - adds the
SqlParameter
list as an array to the SqlCommand.ParametersAddRange()
Usage of the Classes
We now can create a generic method that calls the SqlCommands
class with the needed data. All inserts in your database can go over this class.
Even this class will be in the Data Access Layer.
public static Int32 InsertData(String StoredProcedure,
List<SqlParameter> parms, String ConnectionName)
{
SqlCommand myCommand = null;
Int32 affectedRows = 0;
try
{
myCommand = SQLCommands.OpenCommand
(StoredProcedure, parms, ConnectionName);
affectedRows = myCommand.ExecuteNonQuery();
}
catch (Exception err)
{
string error = err.ToString();
}
finally
{
SQLCommands.CloseCommand(myCommand);
}
return affectedRows;
}
I call InsertData
with the needed values in my code with:
int recordAdded = InsertData("mystoredprocedurename", null, null);
In this case, no sqlparameter
s are added to the command.Parameters
because it is possible my stored procedure does not need any parameters. The OpenCommand
method will search for a connectionstring
in the config file.
Or call it like this:
int recordAdded = InsertData("mystoredprocedurename",
Store.SetSqlParameters(GetSomeObject()), null);
GetSomeObject
does a request and creates SOmeObject
which will be set to a list of sqlparameter
s. The parameters are checked for null
values and are added to command.Parameters.AddRange()
and set to an array. The OpenCommand
method will search for a connectionstring
in the config file.
Or call it like this:
int recordAdded = InsertData("mystoredprocedurename",
Store.SetSqlParameters(GetSomeObject()), "myConnectionName");
GetSomeObject
does a request and creates SOmeObject
which will be set to a list of sqlparameter
s. The parameters are checked for null
values and are added to command.Parameters.AddRange()
and set to an array. The OpenCommand
method will use the given connectionstring name.
Findings
It is of course possible to extend the class that holds the InsertData
method with the next methods:
public static Int32 InsertData(String StoredProcedure)
{
return InsertData(StoredProcedure, null, null);
}
public static Int32 InsertData(String StoredProcedure, List<SqlParameter> parms)
{
return InsertData(StoredProcedure, parms, null);
}
public static Int32 InsertData(String StoredProcedure, String ConnectionName)
{
return InsertData(StoredProcedure, ConnectionName);
}
History
- 5th August, 2011: Initial version
- 9th August, 2011: Article updated