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