SQLCommand.ExecuteReader() does not restrict to read only statements
So apparently, ExecuteReader is used for read only and ExecuteNonQuery is used for transactions. But for some reason even when I used ExecuteReader I am still able to run write (Insert, Update, Delete) commands (typed in textbox1). Is there something wrong with my code or am I misunderstanding the way ExecuteReader is supposed to work?
//MY CODE
string sqlStatement = textbox1.Text;
System.Data.SqlClient.SqlConnectionStringBuilder builder =
new System.Data.SqlClient.SqlConnectionStringBuilder();
builder.DataSource = ActiveServer;
builder.IntegratedSecurity = true;
System.Data.SqlClient.SqlConnection Connection = new
System.Data.SqlClient.SqlConnection(builder.ConnectionString);
Connection.Open();
System.Data.SqlClient.SqlCommand command = new
System.Data.SqlClient.SqlCommand(sqlStatement, Connection);
System.Data.SqlClient.SqlDataReader reader = command.ExecuteReader();
dataGridView1.AutoGenerateColumns = true;
bindingSource1.DataSource = reader;
dataGridView1.DataSource = bindingSource1;
reader开发者_运维技巧.Close();
Connection.Close();
ExecuteReader
simply returns a reader that can read rows returned from a SQL procedure - it doesn't stop you from running arbitrary SQL on the way to providing that result set.
Doing inserts / updates / deletes and then immediately returning a result set (so from code looking like a read) is arguably a little odd (read: code smell), and should be reviewed to see if it can be split into distinct actions.
Though both executes the sql, ExecuteReader
is expected to return records while ExecuteNonQuery
number of records affected. Both are different hence. But internally how different they are would depend on vendor specific implementation. You could use ExecuteReader
alone for all your db actions because it just worked (until now) but since its not documented its not really the right approach to go for. You could be more clear about your intent with ExecuteNonQuery
.
As far as performance is considered I don't think there's a difference at all. I tried with SQLite
, MySqlClient
, SqlClient
, SqlServerCe
and VistaDb
and saw no noticeable difference to favour either. And they all should use ExecuteReader
internally one way or other.
The essentials:
SqlClient:
private int InternalExecuteNonQuery(DbAsyncResult result, string methodName, bool sendToPipe)
{
if (!this._activeConnection.IsContextConnection)
{
if (this.BatchRPCMode || CommandType.Text != this.CommandType || this.GetParameterCount(this._parameters) != 0)
{
Bid.Trace("<sc.SqlCommand.ExecuteNonQuery|INFO> %d#, Command executed as RPC.\n", this.ObjectID);
SqlDataReader sqlDataReader = this.RunExecuteReader(CommandBehavior.Default, RunBehavior.UntilDone, false, methodName, result);
if (sqlDataReader == null)
{
goto IL_E5;
}
sqlDataReader.Close();
goto IL_E5;
}
IL_B5:
this.RunExecuteNonQueryTds(methodName, flag);
}
else
{
this.RunExecuteNonQuerySmi(sendToPipe);
}
IL_E5:
return this._rowsAffected;
}
and
MySqlClient:
public override int ExecuteNonQuery()
{
int records = -1;
#if !CF
// give our interceptors a shot at it first
if ( connection != null &&
connection.commandInterceptor != null &&
connection.commandInterceptor.ExecuteNonQuery(CommandText, ref records))
return records;
#endif
// ok, none of our interceptors handled this so we default
using (MySqlDataReader reader = ExecuteReader())
{
reader.Close();
return reader.RecordsAffected;
}
}
As you can see MySqlClient
directly calls ExecuteReader
while SqlClient
does only for certain conditions. Mind you insert
s and update
s are rarely the bottleneck (its often the select
s).
As I said you wouldn't get the number of rows affected with the help of ExecuteReader
, so use ExecuteNonQuery
better to execute queries. A more direct replacement by ExecuteReader
would be of ExecuteScalar
which returns the data in first column of first row read.
The essentials:
SqlClient:
override public object ExecuteScalar()
{
SqlConnection.ExecutePermission.Demand();
// Reset _pendingCancel upon entry into any Execute - used to synchronize state
// between entry into Execute* API and the thread obtaining the stateObject.
_pendingCancel = false;
SqlStatistics statistics = null;
IntPtr hscp;
Bid.ScopeEnter(out hscp, "<sc.sqlcommand.executescalar|api> %d#", ObjectID);
try
{
statistics = SqlStatistics.StartTimer(Statistics);
SqlDataReader ds = RunExecuteReader(0, RunBehavior.ReturnImmediately, true, ADP.ExecuteScalar);
object retResult = null;
try
{
if (ds.Read())
{
if (ds.FieldCount > 0)
{
retResult = ds.GetValue(0);
}
}
return retResult;
}
finally
{
// clean off the wire
ds.Close();
}
}
finally
{
SqlStatistics.StopTimer(statistics);
Bid.ScopeLeave(ref hscp);
}
}
and
MySqlClient:
public override object ExecuteScalar()
{
lastInsertedId = -1;
object val = null;
#if !CF
// give our interceptors a shot at it first
if (connection != null &&
connection.commandInterceptor.ExecuteScalar(CommandText, ref val))
return val;
#endif
using (MySqlDataReader reader = ExecuteReader())
{
if (reader.Read())
val = reader.GetValue(0);
}
return val;
}
So it doesn't hurt to use ExecuteReader
for ExecuteScalar
and no performance difference whatsoever..
The underlying implementation of those methods simply executes the given SQL statement, so you can run pretty much any statement with any of those methods. It's just that the end result is that a reader tries to return an expected result set, while the other method does not expect a result set. It is probably a little less efficient to use ExecuteReader to run a statement that does not produce a result set. The provider will still create a data reader object, which would add a little bit of cost (probably negligible in most cases unless the provider ends up making another round-trip call to the server).
精彩评论