I am adding MiniProfiler to our existing custom ORM which is based on ADO.Net. This link indicates that I just need to wrap the DbConnection with a new instance of ProfiledDbConnection
but when I do that all or most of the queries performed through this connection fail with the message below (or similar, not everything is ExecuteScalar
). When the DbConnection is no longer wrapped then there is no longer a problem. Is there perhaps an additional step I need to perform for MiniProfiler to work with ADO.Net?
System.Data.SqlClient.SqlException (0x80131904): Procedure or function 'GetFeature' expects parameter '@Addon', which was not supplied.
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
at System.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
at System.Data.SqlClient.SqlDataReader.get_MetaData()
at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString, Boolean isInternal, Boolean forDescribeParameterEncryption, Boolean shouldCacheForAlwaysEncrypted)
at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, Boolean inRetry, SqlDataReader ds, Boolean describeParameterEncryptionRequest)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
at System.Data.SqlClient.SqlCommand.ExecuteScalar()
at StackExchange.Profiling.Data.ProfiledDbCommand.ExecuteScalar()
and another example (there are many)
Procedure or function 'CampaignInProgress' expects parameter '@CampaignId', which was not supplied.|System.Data.SqlClient.SqlException (0x80131904): Procedure or function 'CampaignContactsInProgressGet' expects parameter '@CampaignId', which was not supplied.
at System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
at System.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
at System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, Boolean callerHasConnectionLock, Boolean asyncClose)
at System.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, Boolean& dataReady)
at System.Data.SqlClient.SqlDataReader.TryConsumeMetaData()
at System.Data.SqlClient.SqlDataReader.get_MetaData()
at System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString, Boolean isInternal, Boolean forDescribeParameterEncryption, Boolean shouldCacheForAlwaysEncrypted)
at System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async, Int32 timeout, Task& task, Boolean asyncWrite, Boolean inRetry, SqlDataReader ds, Boolean describeParameterEncryptionRequest)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, TaskCompletionSource`1 completion, Int32 timeout, Task& task, Boolean& usedCache, Boolean asyncWrite, Boolean inRetry)
at System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method)
at System.Data.SqlClient.SqlCommand.ExecuteReader(CommandBehavior behavior, String method)
at System.Data.SqlClient.SqlCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.ExecuteReader(CommandBehavior behavior)
at StackExchange.Profiling.Data.ProfiledDbCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.ExecuteReader(CommandBehavior behavior)
Edit:
CampaignInProgress
and GetFeature
are stored procedures. I would like to re-iterate that this is a custom 3rd party ORM which I do not have much detail on. Most of the code shown below is from R# decompilation.
You call the method like:
var parms = new List<object>
{
new GenericParameter(parameterName: "@CampaignId", dbType: DbType.Int64, value: campaignId)
};
using (var ctx = new DbContext(connStr))
{
ctx.FillWithSp(result, "dbo.CampaignInProgress", parms.ToArray());
}
DbContext looks like and was where I added the MiniProfiler wrapper:
public DbContext(string connectionStr) : base(ConnectionManager.GetConnection(connectionStr)) { }
Next it passes through like so:
return Adapter<TDC>().FillWithSP(collection, storedProcedure, parameters);
Then:
public int FillWithSP(
ICollection<T> collection,
string storedProcName,
params object[] parameters)
{
if (storedProcName == null)
throw new ArgumentNullException(nameof (storedProcName));
return this.FillInternal(collection, storedProcName, FillMethod.StoredProcedure, (IList) parameters);
}
Then
private int FillInternal(
ICollection<T> collection,
string text,
FillMethod method,
IList parameters)
{
if (collection == null)
throw new ArgumentNullException(nameof (collection));
if (this.Connection == null)
throw new InvalidOperationException("must set connection before calling Fill()");
using (DbDataReader reader = this.ExecuteReaderInternal(this.PrepCommandForSelect(text, method, parameters), CommandBehavior.SingleResult))
return this.PopulateCollection(collection, reader);
}
PrepCommandForSelect:
private DbCommand PrepCommandForSelect(
string text,
FillMethod method,
IList parameters)
{
DbCommand command = (DbCommand) null;
switch (method)
{
// Other cases not relevant
case FillMethod.StoredProcedure:
command = this.Connection.CreateCommand();
command.CommandText = text;
command.CommandType = CommandType.StoredProcedure;
DataManager.AddParamsToCommand(command, this.SqlDialect, parameters, this.ParameterPrefix);
break;
}
return command;
}
AddParamsToCommand:
public static void AddParamsToCommand(
DbCommand command,
SqlDialect dialect,
IList parameters,
char placeholderPrefix)
{
if (command == null)
throw new ArgumentNullException(nameof (command));
if (parameters == null)
return;
for (int index = 0; index < parameters.Count; ++index)
{
if (parameters[index] is DbParameter)
command.Parameters.Add(parameters[index]);
else if (parameters[index] is GenericParameter)
{
GenericParameter parameter = (GenericParameter) parameters[index];
if (parameter.ParameterName == null && command.CommandType == CommandType.Text)
// Not relevant
else
parameter.AddToCommand(command, dialect);
}
else if (command.CommandType == CommandType.Text)
{
// Not relevant
}
else
{
// Not relevant
}
}
}
AddToCommand
public void AddToCommand(DbCommand command, SqlDialect dialect)
{
if (command == null)
throw new ArgumentNullException(nameof (command));
if (dialect == null)
throw new ArgumentNullException(nameof (dialect));
this._nativeParam = command.CreateParameter();
if (this._parameterName != null || dialect.UseNamedParameters || command.CommandType == CommandType.StoredProcedure)
this._nativeParam.ParameterName = dialect.FormatParameterName(this._parameterName, command.CommandType);
this._nativeParam.Direction = this._direction;
if (this._dbType != DbType.Object)
{
if (this._nativeParam is SqlParameter && this._dbType == DbType.Time)
((SqlParameter) this._nativeParam).SqlDbType = SqlDbType.Time;
else
this._nativeParam.DbType = this._dbType;
}
if (this._size != 0)
this._nativeParam.Size = this._size;
this._nativeParam.SourceColumn = this._sourceColumn;
this._nativeParam.Value = this._value ?? (object) DBNull.Value;
command.Parameters.Add((object) this._nativeParam);
}
FormatParameterName
public override string FormatParameterName(string name, CommandType commandType)
{
if (string.IsNullOrEmpty(name))
throw new ArgumentNullException(nameof (name));
return (name[0] == '@' ? name : "@" + name).Replace(' ', '_');
}
Then back to FillInternal
to method ExecuteReaderInternal
private DbDataReader ExecuteReaderInternal(
DbCommand command,
CommandBehavior behavior)
{
this.SetCommandTimeout((IDbCommand) command);
this.EnrollInTransaction(command);
this.DoBeforeExecuteCommand(command);
return command.ExecuteReader(behavior); // System.Data.Common
}
At this point you can pick up from the stack trace. I am not seeing anything that would cause the parameter to be named incorrectly. Keep in mind this code works when it is not wrapped in the MiniProfiler.
User contributions licensed under CC BY-SA 3.0