I wrote the following method
public async Task<DataTable> ExecuteProcedureToDataTableAsync(string spName, object parameters, int? commandTimeout = null, bool userPrefix = false)
{
using (var connection = new SqlConnection(_ConnectionString))
{
string spNameWithPrefix = GetSpNameWithPrefix(spName, userPrefix);
var dt = new DataTable();
_Logger.Debug($"Executing Query: [{spNameWithPrefix}], with params:[{parameters.ToJsonString()}]");
dt.Load(await connection.ExecuteReaderAsync(spNameWithPrefix, parameters, commandTimeout: commandTimeout, commandType: CommandType.StoredProcedure));
_Logger.Debug($"Completed Query To DataTable: [{spNameWithPrefix}], result columnCount:[{dt.Columns.Count}], result row count:[{dt.Rows.Count}]");
return dt;
}
}
and invoke it like so:
using (var results = await ExecuteProcedureToDataTableAsync(StoredProcedureFullName, StoredProcedureParams, Timeout, userPrefix: false))
{
ExportReport(requestModel, results);
}
and I get the exception:
MultiExec is not supported by ExecuteReader
is it not supported to executeReader with spr ?
Multiple executions aren't supported by ExecuteReader. Use QueryMultiple instead.
Related
I have a question in relation to this one:
MySqlCommand call function
Using MariaDB
10.5.11-MariaDB-1:10.5.11+maria~focal
and the given function (simplified)
CREATE FUNCTION `activity_plugin_active`() RETURNS binary(1)
BEGIN
DECLARE result INT DEFAULT 0;
RETURN result;
END
To get the result in .NET of the function I use a helper method
bool activitiesPluginActive = 1 == ExecuteIntScalarAsync(
"activity_plugin_active",
null,
CancellationToken.None)
.GetAwaiter().GetResult();
But this method is a bit bulky
private async Task<int> ExecuteIntScalarAsync(
string functionName,
IEnumerable<DbParameter> parameters,
CancellationToken cancellationToken = default)
{
using (MySqlConnection conn = new MySqlConnection(databaseConfiguration.GetConnectionString()))
{
await conn.OpenAsync();
MySqlCommand cmd = new MySqlCommand(functionName)
{
CommandType = CommandType.StoredProcedure,
Connection = conn
};
if (parameters != null)
foreach (var parameter in parameters)
cmd.Parameters.Add(parameter);
var returnValue = new MySqlParameter("returnvalue", MySqlDbType.Int32)
{
Direction = ParameterDirection.ReturnValue
};
cmd.Parameters.Add(returnValue);
logger.LogInformation($"Executing { functionName }");
await cmd.ExecuteScalarAsync(cancellationToken);
// return value is byte[1], value 48
byte[] bytes = returnValue.Value as byte[];
// need to cast to string, value "0"
var sValue = System.Text.Encoding.Default.GetString(bytes);
// need to cast to int
return Convert.ToInt32(sValue);
}
}
As you see in the code comments above the return value is a byte[1] - with the given function it has the value 48 - ASCII CODE for number zero.
Although the return type is defined as MySqlDbType.Int32.
How do I get directly returned an integer without the need to cast twice?
It is because in your function you are specifying that return the result of function as binary(1) (i.e. RETURNS binary(1)) . Can you try changing it to INT like:
CREATE FUNCTION `activity_plugin_active`() RETURNS INT
BEGIN
DECLARE result INT DEFAULT 0;
RETURN result;
END
I am using Dapper and want to return Today's Sales amount using a Stored Procedure but if there is no sale for today, it returns empty values which in turn throws an exception "'Object reference not set to an instance of an object." at the line with the Query.
How to handle that exception?
Code Used
public decimal GetAllSaleTotal(string connectionStringName = "POS")
{
string connectionString = GetConnectionString(connectionStringName);
using (IDbConnection connection = new SqlConnection(connectionString))
{
var result = connection.Query<decimal>("dbo.GetTotalSales", commandType:
CommandType.StoredProcedure).First();
return result;
}
}
Why don't you handle the exception using try..catch statement.
Also, you should use
var result = connection.Query<decimal?>("dbo.GetTotalSales", commandType: CommandType.StoredProcedure).FirstOrDefault();
Here's how I might handle it. Use Take to get 0 or 1 rows, then check the length.
public decimal GetAllSaleTotal(string connectionStringName = "POS")
{
string connectionString = GetConnectionString(connectionStringName);
using (IDbConnection connection = new SqlConnection(connectionString))
{
var result = connection.Query<decimal>
(
commandText: "dbo.GetTotalSales",
commandType: CommandType.StoredProcedure
).Take(1).ToList();
return result.Count > 0 ? result[0] : 0M;
}
}
I migrated an Access database to SQL using Microsoft SQL Server Migration Assistant.
Now, I am having trouble reading data.
return reader.GetInt32(0); Throws Invalid attempt to read when no data is present exception after 32 rows retrieved. If I add CommandBehavior.SequentialAccess to my command, I am able to read 265 rows, every time.
The data
The query (in SQL Management Studio):
SELECT *
FROM Products2
WHERE Products2.PgId = 337;
There is nothing special about row 32, and if I reverse the order, it is still row 32 that kills it.
Row 265, just for good measure.
The code
The query:
SELECT *
FROM Products2
WHERE Products2.PgId = #productGroupId;
The parameter:
Name = "productGroupId"
Value = 337
The execution:
public async Task ExecuteAsync(string query, Action<IDataReader> onExecute, params DataParameter[] parameters)
{
using(var command = builder.BuildCommand(query))
{
foreach(var parameter in parameters)
command.AddParameter(parameter);
if(!connection.IsOpen)
connection.Open();
await Task.Run(async () =>
{
using(var reader = await command.ExecuteAsync())
if(await reader.ReadAsync())
onExecute(reader);
});
}
}
And reading:
await executor.ExecuteAsync(query, async reader =>
{
do
{
products.Add(GetProduct(reader));
columnValues.Enqueue(GetColumnValues(reader).ToList());
} while(await reader.ReadAsync());
}, parameters.ToArray());
await reader.ReadAsync() returns true, but when GetProduct(reader) calls reader.GetInt32(0); for the 32nd time, it throws the exception.
It works fine if the data is less than 32 rows, or 265 in case of SequentialAccess.
I tried increasing the CommandTimeout, but it didn't help. When I swap the connection to OleDb again, it works just fine.
Thanks in advance.
EDIT
If I replace * in the query with just a few specific columns, it works. When I read ~12 columns, it fails, but later than row 32.
As per request, GetProduct:
private Product GetProduct(IDataReader productReader)
{
return new Product
{
Id = productReader.ReadLong(0),
Number = productReader.ReadString(2),
EanNo = productReader.ReadString(3),
Frequency = productReader.ReadInt(4),
Status = productReader.ReadInt(5),
NameId = productReader.ReadLong(6),
KMPI = productReader.ReadByte(7),
OEM = productReader.ReadString(8),
CurvesetId = productReader.ReadInt(9),
HasServiceInfo = Convert.ToBoolean(productReader.ReadByte(10)),
ColumnData = new List<ColumnData>()
};
}
GetColumnData:
private IEnumerable<long> GetColumnValues(IDataReader productReader)
{
var columnValues = new List<long>();
int columnIndex = 11;
while(!productReader.IsNull(columnIndex))
columnValues.Add(productReader.ReadLong(columnIndex++));
return columnValues;
}
And the adapter:
public long ReadLong(int columnIndex)
{
return reader.GetInt32(columnIndex);
}
Alright, it is getting long. :)
Thanks to #Igor, I tried creating a small working example. This seem to work fine:
private static async Task Run()
{
var result = new List<Product>();
string conString = #" ... ";
var con = new SqlConnection(conString);
con.Open();
using(var command = new SqlCommand("SELECT * FROM Products2 WHERE Products2.PgId = #p;", con))
{
command.Parameters.Add(new SqlParameter("p", 337));
await Task.Run(async () =>
{
using(var productReader = await command.ExecuteReaderAsync())
while(await productReader.ReadAsync())
{
result.Add(new Product
{
Id = productReader.GetInt32(0),
Number = productReader.GetString(2),
EanNo = productReader.GetString(3),
Frequency = productReader.GetInt16(4),
Status = productReader.GetInt16(5),
NameId = productReader.GetInt32(6),
KMPI = productReader.GetByte(7),
OEM = productReader.GetString(8),
CurvesetId = productReader.GetInt16(9),
HasServiceInfo = Convert.ToBoolean(productReader.GetByte(10))
});
GetColumnValues(productReader);
}
});
}
Console.WriteLine("End");
}
private static IEnumerable<long> GetColumnValues(SqlDataReader productReader)
{
var columnValues = new List<long>();
int columnIndex = 11;
while(!productReader.IsDBNull(columnIndex))
columnValues.Add(productReader.GetInt32(columnIndex++));
return columnValues;
}
}
Here's the data in Access, just in case:
I managed to fix the problem.
I still have questions though, since I don't understand why this didn't affect Access.
I changed:
public async Task ExecuteAsync(string query, Action<IDataReader> onExecute, params DataParameter[] parameters)
{
using(var command = builder.BuildCommand(query))
{
foreach(var parameter in parameters)
command.AddParameter(parameter);
if(!connection.IsOpen)
connection.Open();
await Task.Run(async () =>
{
using(var reader = await command.ExecuteAsync())
if(await reader.ReadAsync())
onExecute(reader);
});
}
}
To:
public async Task ExecuteAsync(string query, Func<IDataReader, Task> onExecute, params DataParameter[] parameters)
{
using(var command = builder.BuildCommand(query))
{
foreach(var parameter in parameters)
command.AddParameter(parameter);
if(!connection.IsOpen)
connection.Open();
using(var reader = await command.ExecuteAsync())
await onExecute(reader);
}
}
If someone can explain why this helped, I would really appreciate it.
The below method doesn't compile. Alternatives?
public static async Task<IEnumerable<object[]>> GetRecordsAsync(
this Transaction transaction,
string commandText,
params SqlParameter[] parameters)
{
// Get a SqlDataReader
var reader = await transaction.GetReaderAsync(commandText, parameters);
var fieldCount = -1;
// Begin iterating through records asynchronously
while (await reader.ReadAsync()) // Note we don't loop until .ReadAsync returns a boolean
{
// Grab all the field values out
if (fieldCount < 0)
fieldCount = reader.FieldCount;
var fields = new object[fieldCount];
reader.GetValues(fields);
// Yield return the field values from this record
yield return fields;
}
}
Error message:
The body of 'TransactionExtensions.GetRecordsAsync(Transaction, string, params SqlParameter[])' cannot be an iterator block because 'Task>' is not an iterator interface type
I don't see a way to adapt this answer to a similar sounding (but different) question, because I don't know a priori how many times the loop will go.
Edit: fixed formatting
Based on #SLaks's comment to the question, here's a general alternative using Reactive Extensions:
/// <summary>
/// Turns the given asynchronous functions into an IObservable
/// </summary>
static IObservable<T> ToObservable<T>(
Func<Task<bool>> shouldLoopAsync,
Func<Task<T>> getAsync)
{
return Observable.Create<T>(
observer => Task.Run(async () =>
{
while (await shouldLoopAsync())
{
var value = await getAsync();
observer.OnNext(value);
}
observer.OnCompleted();
}
)
);
}
Example usage, tailored to solve the question's specific case:
/// <summary>
/// Asynchronously processes each record of the given reader using the given handler
/// </summary>
static async Task ProcessResultsAsync(this SqlDataReader reader, Action<object[]> fieldsHandler)
{
// Set up async functions for the reader
var shouldLoopAsync = (Func<Task<bool>>)reader.ReadAsync;
var getAsync = new Func<SqlDataReader, Func<Task<object[]>>>(_reader =>
{
var fieldCount = -1;
return () => Task.Run(() =>
{
Interlocked.CompareExchange(ref fieldCount, _reader.FieldCount, -1);
var fields = new object[fieldCount];
_reader.GetValues(fields);
return fields;
});
})(reader);
// Turn the async functions into an IObservable
var observable = ToObservable(shouldLoopAsync, getAsync);
// Process the fields as they become available
var finished = new ManualResetEventSlim(); // This will be our signal for when the observable completes
using (observable.Subscribe(
onNext: fieldsHandler, // Invoke the handler for each set of fields
onCompleted: finished.Set // Set the gate when the observable completes
)) // Don't forget best practice of disposing IDisposables
// Asynchronously wait for the gate to be set
await Task.Run((Action)finished.Wait);
}
(Note that getAsync could be simplified in the above code block, but I like how explicit it is about the closure that's being created)
...and finally:
// Get a SqlDataReader
var reader = await transaction.GetReaderAsync(commandText, parameters);
// Do something with the records
await reader.ProcessResultsAsync(fields => { /* Code here to process each record */ });
Don't return a Task<IEnumerable<T>> and don't even use Task at all for this; instead, return an IAsyncEnumerable<T>. No need for third-party libraries or other workarounds, no need to even alter the body of your original method.
public static async IAsyncEnumerable<object[]> GetRecordsAsync(
this Transaction transaction,
string commandText,
params SqlParameter[] parameters)
{
// Get a SqlDataReader
var reader = await transaction.GetReaderAsync(commandText, parameters);
var fieldCount = -1;
// Begin iterating through records asynchronously
while (await reader.ReadAsync()) // Note we don't loop until .ReadAsync returns a boolean
{
// Grab all the field values out
if (fieldCount < 0)
fieldCount = reader.FieldCount;
var fields = new object[fieldCount];
reader.GetValues(fields);
// Yield return the field values from this record
yield return fields;
}
}
I solved it without third-party extensions:
public async Task<IEnumerable<Item>> GetAllFromDb()
{
OracleConnection connection = null;
DbDataReader reader = null;
try
{
connection = new OracleConnection(connectionString);
var command = new OracleCommand(queryString, connection);
connection.Open();
reader = await command.ExecuteReaderAsync();
return this.BuildEnumerable(connection, reader);
}
catch (Exception)
{
reader?.Dispose();
connection?.Dispose();
throw;
}
}
private IEnumerable<Item> BuildEnumerable(OracleConnection connection, DbDataReader reader)
{
using (connection)
using (reader)
{
while (reader.Read())
{
var item = new Item()
{
Prop = reader.GetString(0),
};
yield return item;
}
}
}
This example is for Oracle Data Reader but the same approach is applicable to any asynchronous operation combined with yield return
I use EnterpriseLibrary to access database with async method.but it is error this:"Invalid operation. The connection is closed."
my code:
public Database CreateDatabase()
{
if (null == this._createDatabase)
{
this._createDatabase = new DatabaseProviderFactory().Create("ConnectionString");
}
return this._createDatabase;
}
public async Task<bool> IsExistCode(string code)
{
Database db = base.CreateDatabase();
using (DbCommand dbCommand = db.GetSqlStringCommand("select top 1 1 from Ads WITH(NOLOCK) where Code=#Code"))
{
db.AddInParameter(dbCommand, "Code", DbType.AnsiString, code);
var result = await dbCommand.ExecuteScalarAsync();//error occurrence
return null != result;
}
}
ps:sync method(dbCommand.ExecuteScalar()) is ok.
Note:my way to use is wrong,see:https://msdn.microsoft.com/zh-cn/library/hh211418.aspx