开发者

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 inserts and updates are rarely the bottleneck (its often the selects).

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).

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜