Pros and Cons of using SqlCommand Prepare in C#?
When i was reading books to learn C# (might be some old Visual Studio 2005
books) I've encountered advice to always use SqlCommand.Prepare
everytime I execute SQL call (whether its' a SELECT
/UPDATE
or INSERT
on SQL SERVER 2005/2008) and I pass parameters to it. But is it really so?
Should it be done every time? Or just sometimes?
Does it matter whether it's one parameter being passed or five or twenty?
What boost should it give if any? Would it be noticeable at all (I've been using
SqlCommand.Prepare
here and skipped it there and never had any problems or noticeable differences).
For the sake of the question this is my usual code that I use, but this is more of a general question.
public static decimal pobierzBenchmarkKolejny(string varPortfelID, DateTime data, decimal varBenchmarkPoprzedni, decimal varStopaOdniesienia) {
const string preparedCommand = @"SELECT [dbo].[ufn_BenchmarkKolejny](@varPortfelID, @data, @varBenchmarkPoprzedni, @varStopaOdniesienia) AS 'Benchmark'";
using (var varConnection = Locale.sqlConnectOneTime(Locale.sqlDataConnectionDetailsDZP)) //if (varConnection != null) {
using (var sqlQuery = new SqlCommand(preparedCommand, varConnection)) {
sqlQuery.Prepare();
sqlQuery.Parameters.AddWithValue("@varPortfelID", varPortfelID);
sqlQuery.Parameters.AddWithValue("@varStopaOdniesienia", varStopaOdniesienia);
sqlQuery.Parameters.AddWithValue("@data", data);
sqlQuery.Parameters.AddWithValue("@varBenchmarkPoprzedni", varBenchmarkPoprzedni);
using (var sqlQueryResult = sqlQuery.ExecuteReader())
if (sqlQueryResult != null) {
while (sqlQueryResult.Read()) {
}
}
}
}
Additional clarification:
If i move sqlQuery.Prepare()
like in code below exception is thrown that the size has to be explicitly declared, which basically leads me to thinking that having sqlQuery.Prepare()
as first makes it useless? Can someone show the proper usage using my example?
public static decimal pobierzBenchmarkKolejny(string varPortfelID, DateTime data, decimal varBenchmarkPoprzedni, decimal varStopaOdniesienia) {
const string preparedCommand = @"SELECT [dbo].[ufn_BenchmarkKolejny](@varPortfelID, @data, @varBenchmarkPoprzedni, @varStopaOdniesienia) AS 'Benchmark'";
using (var varConnection = Locale.sqlConnectOneTime(Locale.sqlDataConnectionDetailsDZP)) //if (varConnection != null) {
using (var sqlQuery = new SqlCommand(preparedCommand, varConnection)) {
sqlQuery.Parameters.AddWithValue("@varPortfelID", varPortfelID);
sqlQuery.Parameters.AddWithValue("@varStopaOdniesienia", varStopaOdniesienia);
sqlQuery.Parameters.AddWithValue("@data", data);
sqlQuery.Parameters.AddWithValue("@varBenchmarkPoprzedni", varBenchmarkPoprzedni);
sqlQuery.Prepare();
using (var sqlQueryResult = sqlQuery.ExecuteReader())
if (sqlQueryResult != null) {
while (sqlQueryResult.Read()) {
}
}
}
}
How would I do that? By adding .size next to parameters and doing varPortfel.Lenght if it's a string etc?
From the MSDN Documentation:
"Before you call Prepare, specify the data type of each parameter in the statement to be prepared. For each parameter that has a variable length data type, you must set the Size property to the maximum size needed. Prepare returns an error if these conditions are not met.
If you call an Execute method after calling Prepare, any parameter value that is larger than the value specified by the Size property is automatically truncated to the original specified size of the parameter, and no truncation errors are returned.
Output parameters (whether prepared or not) must have a user-specified data type. If you specify a variable length data type, you must also specify the maximum Size."
Furthermore, "If the CommandType property is set to TableDirect, Prepare does nothing. If CommandType is set to StoredProcedure, the call to Prepare should succeed, ..."
This in general is used to make sure that the end user is not using a SQL Injection technique to add or remove information you do not want them too from the database.
I looked into it and check out this article http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.prepare.aspx. Your issue is you need to define your parameters before you run .Prepare() and then set your parameters after you run .Prepare(). Right now you are doing both before. I would try something like this (Note I didn't test it so my syntax might be a bit off).
public static decimal pobierzBenchmarkKolejny(string varPortfelID, DateTime data, decimal varBenchmarkPoprzedni, decimal varStopaOdniesienia) {
const string preparedCommand = @"SELECT [dbo].[ufn_BenchmarkKolejny](@varPortfelID, @data, @varBenchmarkPoprzedni, @varStopaOdniesienia) AS 'Benchmark'";
using (var varConnection = Locale.sqlConnectOneTime(Locale.sqlDataConnectionDetailsDZP)) //if (varConnection != null) {
using (var sqlQuery = new SqlCommand(preparedCommand, varConnection)) {
sqlQuery.Parameters.Add("@varPortfelID");
sqlQuery.Parameters.Add("@varStopaOdniesienia");
sqlQuery.Parameters.Add("@data");
sqlQuery.Parameters.Add("@varBenchmarkPoprzedni");
sqlQuery.Prepare();
sqlQuery.ExecuteNonQuery();//This might need to be ExecuteReader()
sqlQuery.Parameters[0].Value = varPortfelID;
sqlQuery.Parameters[1].Value = varStopaOdniesienia;
sqlQuery.Parameters[2].Value = data;
sqlQuery.Parameters[3].Value = varBenchmarkPoprzedni;
using (var sqlQueryResult = sqlQuery.ExecuteReader())
if (sqlQueryResult != null) {
while (sqlQueryResult.Read()) {
}
}
}
}
The other benefit is that by doing this the SQL query plan is compiled, cached and re-used. This is not a big deal if for a low volume of calls to your query but if you have a lot there really are some significant performance advantages to doing this.
From my own experience: the performance boost is VERY significant. Some time ago I worked on a project where we used our own object-relational mapping. We utilized huge database as a persistent store of complex object model - with on-demand object loading and weak-referenced object life-time.
Using prepared commands was crucial for success of that application, as it alone made the system actually usable.
In other words: if you execute many SQL commands - that are exactly same or differ only in parameter values -, you'll see huge performance boost.
I don't have exact numbers or links, but I can testify my own experience.
According to IDbCommand.Prepare Method documentation:
The server automatically caches plans for reuse as necessary; therefore, there is no need to call this method directly in your client application.
I also found this Databases Administrators answer which give you a lot of details about the prepare
method and why I didn't get any significant improvement.
About the SQL injection, you'll be protected against it because you have used a parameterized sqlCommand... not because you have called the prepare method.