How to use Dapper asynchronously with multiple stored procedure calls? - asp.net

I'm using the Dapper ORM in an ASP.Net Core 2.1 CRUD application, with a SQL Server database. For a new form I'm developing, I want to retrieve all reference data (to populate SELECT field options) in one go when the screen is invoked. This involves a series of stored procedure calls, which I want to do asynchronously for best performance.
This Dapper tutorial suggests I should look at using QueryMultipleAsync, but I can't find an example of its use with stored procedures, only hard-coded SQL statements.
My C# code currently looks like this:
public async Task<ContactReferenceData> Get()
{
ContactReferenceData refData = new ContactReferenceData();
try
{
using (IDbConnection dbConnection = _connection)
{
dbConnection.Open();
var countryData = await dbConnection.QueryAsync<Country>(sql: "usp_GetCountries", commandType: CommandType.StoredProcedure);
refData.CountryDetails = countryData.AsList();
var companyData = await dbConnection.QueryAsync<Company>(sql: "usp_GetCompanies", commandType: CommandType.StoredProcedure);
refData.CompanyDetails = companyData.AsList();
var groupData = await dbConnection.QueryAsync<Group>(sql: "usp_GetGroups", commandType: CommandType.StoredProcedure);
refData.GroupDetails = groupData.AsList();
var groupPositionData = await dbConnection.QueryAsync<GroupPosition>(sql: "usp_GetGroupPositions", commandType: CommandType.StoredProcedure);
refData.GroupPositionDetails = groupPositionData.AsList();
}
return refData;
}
catch (Exception ex)
{
_logger.LogError(ex.ToString());
throw;
}
}
This works OK in my test environment, but I'm not sure it's the correct way to execute async queries. In particular, I have the following concerns:
is it robust enough to be trusted in live operation?
in its current form, is it maximising the benefits (if any) of asynchronous operation, or should I be using QueryMultipleAsync to properly achieve this?

Have you tried something like this?
public async Task<ContactReferenceData> Get()
{
var sql = "EXEC usp_GetCountries; EXEC usp_GetCompanies; EXEC usp_GetGroups; EXEC usp_GetGroupPositions";
ContactReferenceData refData = new ContactReferenceData();
try
{
using (IDbConnection dbConnection = _connection)
{
using (var multi = connection.QueryMultipleAsync(sql: sql, commandType: CommandType.StoredProcedure ).Result)
{
refData.CountryDetails = multi.Read<countryDetails>().ToList();
refData.CompanyDetails = multi.Read<CompanyDetails>().ToList();
refData.GroupData = multi.Read<Groups>().ToList();
refData.GroupPositionsData= multi.Read<GroupPositions>().ToList();
}
}
return refData;
}
catch (Exception ex)
{
_logger.LogError(ex.ToString());
throw;
}
}

Related

ASP.NET Web API - call database to insert via async method, but it doesn't work

I have this method:
public async static Task ExecuteSpAndForget(string sp, Location location, params SqlParameter[] parameters)
{
using (SqlConnection conn = await GetConnectionAsync(location))
{
SqlCommand cmd = new SqlCommand(sp, conn);
cmd.Parameters.AddRange(parameters);
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandTimeout = Int32.Parse(ConfigurationManager.AppSettings["CommandTimeout"]);
await cmd.ExecuteNonQueryAsync().ConfigureAwait(false);
}
}
public async static Task<SqlConnection> GetConnectionAsync(Location location)
{
SqlConnection conn = new SqlConnection(ConfigurationManager.AppSettings["ConnectionString" + location]);
await conn.OpenAsync().ConfigureAwait(false);
return conn;
}
And in my log filter I applied to WebApiConfig.cs:
config.Filters.Add(new LogFilter());
And in LogFilter(), I call the database to write log
public static int WriteLog(HttpActionContext actionContext, Exception exception = null)
{
\\logic
var logTask = DatabaseHelper.ExecuteSpAndForget("writelogSP", Location.Log, sqlParams.ToArray());
\\return logic
}
When I am debugging locally, this never writes to the database, when I deployed it to IIS, it sometimes write to the database, sometimes not. I tested it locally in a console app, if I wait several seconds after calling before exit, data can be inserted into the database, otherwise no insert happens if no wait.
Why? How can I use it?
What you are referring to is a background task.
There are different ways of going about it. The simplest one is Task.Run with no await
public static int WriteLog(HttpActionContext actionContext, Exception exception = null)
{
\\logic
// fire and forget, no await
var logTask = Task.Run(async () =>
{
await DatabaseHelper.ExecuteSpAndForget("writelogSP", Location.Log, sqlParams.ToArray());
});
\\return logic;
}
You can check out this link https://blog.stephencleary.com/2014/06/fire-and-forget-on-asp-net.html for a better solution.

Execute Dynamic Entity in Database using Dapper

My user send dynamic entity from client-project so, I have to write methods like this
public Task<TUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken)
{
cancellationToken.ThrowIfCancellationRequested();
throw new NotImplementedException();
//string sql = "SELECT * FROM \"IdentityUsers\" WHERE \"NormalizedUserName\" = #NormalizedUserName;";
//using (var connection = _databaseConnectionFactory.CreateConnectionAsync())
//{
// connection.QueryFirstOrDefaultAsync<TUser>(sql,
// new { NormalizedUserName = normalizedUserName });
//}
}
My IDatabaseConnectionFactory class bind ConnectionString like below:
public interface IDatabaseConnectionFactory
{
Task<IDbConnection> CreateConnectionAsync();
}
public class ConnectionFactory : IDatabaseConnectionFactory
{
private readonly string _connectionString;
public ConnectionFactory(string connectionString) => _connectionString = connectionString ?? throw new ArgumentNullException(nameof(connectionString));
public async Task<IDbConnection> CreateConnectionAsync()
{
try
{
var connString = new NpgsqlConnection(_connectionString);
await connString.OpenAsync();
return connString;
}
catch
{
throw;
}
}
}
Now, how can I execute following query using generic-type entity TUser
string sql = "SELECT * FROM \"IdentityUsers\" WHERE \"NormalizedUserName\" = #NormalizedUserName;";
using (var connection = _databaseConnectionFactory.CreateConnectionAsync())
{
connection.QueryFirstOrDefaultAsync<TUser>(sql,
new { NormalizedUserName = normalizedUserName });
}
Note: QueryFirstOrDefaultAsync not found under connection here
You aren't awaiting the CreateConnectionAsync. Unfortunately it isn't obvious in this case, because Task<T> is disposable (so the using doesn't complain); try instead:
using (var connection = await _databaseConnectionFactory.CreateConnectionAsync())
{
var user = await connection.QueryFirstOrDefaultAsync<TUser>(sql,
new { NormalizedUserName = normalizedUserName });
}
As a tip: the compiler output (against the original code) helps make this clear:
Error CS1929 'Task<IDbConnection>' does not contain a definition for 'QueryFirstOrDefaultAsync' and the best extension method overload 'SqlMapper.QueryFirstOrDefaultAsync<TUser>(IDbConnection, string, object, IDbTransaction, int?, CommandType?)' requires a receiver of type 'IDbConnection'
which tells us that:
it found some QueryFirstOrDefaultAsync method, but it wasn't usable, because
the target expression is a Task<IDbConnection>, not an IDbConnection
As a side note: it is worth knowing that if you're only doing one operation with the connection, Dapper can deal with opening and closing the connection for you - which can help reduce the number of async/await operations. Consider, for example, if you had a CreateClosedConnection() method that did not open the connection, and thus had no need to be async; the following would still work:
using (var connection = _databaseConnectionFactory.CreateClosedConnection())
{
var user = await connection.QueryFirstOrDefaultAsync<TUser>(sql,
new { NormalizedUserName = normalizedUserName });
}
with Dapper dealing with the await OpenAsync() for you as part of the QueryFirstOrDefaultAsync.

Asp.net async db connection not reusing connections

I have a .net mvc / sql server website for a high-traffic application. I'm using async database connections.
Prior to launch I ran a load test, and it blew up pretty quickly under load. The connection pool quickly ran out of connections.
Running sp_who at the database level shows a pile of connections sleeping awaiting command. If I switch to non-async, this does not happen.
So, it appears that new database calls are not using connections sitting in the connection pool, instead they are opening their own new connection. This quickly exhausts the connection pool limit, and begins timing out queries.
The following is the helper I'm using to execute an async datareader. Does anyone see any issue here?
private async Task<List<T>> ExecuteReaderAsync<T>(SqlCommand command, Func<SqlDataReader, T> rowConverter)
{
List<T> ret = new List<T>();
using (SqlConnection connection = new SqlConnection(this.ConnectionString))
{
command.Connection = connection;
await connection.OpenAsync();
using (SqlDataReader reader = await command.ExecuteReaderAsync())
{
while (await reader.ReadAsync())
{
ret.Add(rowConverter(reader));
}
reader.Close();
}
connection.Close();
}
return ret;
}
I'm calling the code like the datareader helper like the following:
public async Task<Content> FindContentAsync(int id)
{
Content content = null;
using (SqlCommand command = CreateProcedure("dbo.FindContent"))
{
AddParam(command, "Id", SqlDbType.Int, id);
List<Content> items = await ExecuteReaderAsync<Content>(command, x => BindContent(x));
if (items.Count > 0)
{
content = items[0];
}
}
return content;
}
And calling that from a helper:
public async Task<Content> FindAsync(int id)
{
var db = new DataAccess();
var content = await db.FindContentAsync(id);
return content;
}

How to use Transactions in ASP.NET MVC identity 2?

In My ASP.NET MVC5 Identity 2 Application trying to use transactions but it is not working.please see the below code the transactions not working.If var saveteacher = _teacherService.Create(aTeacher); not insert successfully then AspNetUsers not rollback from database.
Code:
using (var dataContext = new SchoolMSDbContext())
{
using (var trans = dataContext.Database.BeginTransaction(IsolationLevel.ReadCommitted))
{
try
{
var adminresult =await UserManager.CreateAsync(user, teacherViewModel.Password);
if (adminresult.Succeeded)
{
aTeacher.Id = user.Id;
var saveteacher = _teacherService.Create(aTeacher);
}
else
{
trans.Rollback();
ModelState.AddModelError("", adminresult.Errors.First());
return View();
}
trans.Commit();
}
catch (Exception ex)
{
trans.Rollback();
Console.WriteLine(ex.InnerException);
}
}
}
I think the problem could be with async stuff.
Try creating the transaction like this:
TransactionScope transaction = new TransactionScope(System.Transactions.TransactionScopeAsyncFlowOption.Enabled);
(you'll have to add System.Transactions) to references.
To commit transaction go transaction.Complete() to rollback do transaction.Dispose().
The problem is that you are creating a new instance of SchoolMSDbContext when you should be acquiring the existing one from the HttpContext.
Example:
using (var dataContext = HttpContext.GetOwinContext().Get<ApplicationDbContext>())
{
//...
}
Make sure _teacherService and UserManager uses same DB Context. No need to create a TransactionScope.

Backup of the database in entity framework

i working with entity famework i need to transfer that code
RESTORE DATABASE [showing8-5-2013] FROM DISK = N'C:\Program Files (x86)\Microsoft SQL Server\MSSQL10_50.SQLEXPRESS\MSSQL\Backup\Company.bak' WITH FILE = 1, NOUNLOAD, REPLACE, STATS = 10
to code Entity frame work
any help thanks
EF is a DB neutral provider concept. Such commands are by their nature DB specific. EF exposes a way to execute an SQL command:
MyContext.Database.ExecuteSqlCommand();
But you may as well just do it directly.
Pass your SQL command into a custom routine eg:
private static bool ExecuteSqlStatement(string connectionString, string statement) {
int rowsAffected;
using (var sqlConnection = new SqlConnection(connectionString)) {
using (var sqlCommand = new SqlCommand(statement, sqlConnection)) {
try {
sqlConnection.Open();
rowsAffected = sqlCommand.ExecuteNonQuery();
}
catch (Exception ex) {
// your handler or re-throw....
return false;
}
}
}
return rowsAffected == -1;
// see http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlcommand.executenonquery.aspx
}

Resources