I have a common dapper function to get list<model> using QueryAsync of dapper
the function looks like below
public async Task<object> QueryAsync(string spName, DynamicParameters p)
{
return await Task.Run(async () =>
{
object obj = new object();
IList objectList = obj as IList;
using (SqlConnection conn = new SqlConnection(_connStr))
{
try
{
conn.Open();
obj = (List<object>)await conn.QueryAsync<object>(sql: spName, param: p, commandType: CommandType.StoredProcedure);
}
catch (Exception ex)
{
Utils.Logger.Instance.LogException(ex);
}
conn.Close();
}
return obj;
});
}
now I am calling this method from my businessLogic Layer like below
public async Task<List<GetTemplates>> GetDocTemplates(string TemplateName, int AccountId)
{
_Parameters.Add("#SP_TemplateName", TemplateName, dbType: DbType.String, direction: ParameterDirection.Input);
_Parameters.Add("#SP_AccountId", AccountId, dbType: DbType.Int32, direction: ParameterDirection.Input);
return (List<GetTemplates>)await _Dapper.QueryAsync("[dbo].[GetDocTemplates]", _Parameters);
}
but I am getting the following error.
Unable to cast object of type
'System.Collections.Generic.List1[System.Object]' to type 'System.Collections.Generic.List1[DocPro.DMS.BusinessEntities.Admin.GetTemplates]'.
I don't know what is wrong with the above code.
Dapper creates the list here. If you want it to be a list of GetTemplates, then you're going to have to tell dapper about that, presumably by making the method generic and calling _Dapper.QueryAsync<GetTemplates>(...). That said... honestly, this method isn't really adding anything except connection setup and logging - the Task.Run is unnecessary, the blind catch that swallows failure is actively dangerous, and DynamicParameters is the least preferred way of passing parameters to dapper. Suggestion:
public async Task<List<T>> QueryListAsync<T>(string spName, object parameters)
{
using var conn = new SqlConnection(_connStr);
try
{
return (await conn.QueryAsync<T>(sql: spName, param: parameters, commandType: CommandType.StoredProcedure)).AsList();
}
catch (Exception ex)
{
Utils.Logger.Instance.LogException(ex);
throw;
}
}
...
public Task<List<GetTemplates>> GetDocTemplates(string TemplateName, int AccountId)
{
return _Dapper.QueryListAsync<GetTemplates>("[dbo].[GetDocTemplates]", new {
SP_TemplateName = TemplateName,
SP_AccountId = AccountId
});
}
Related
I have created a generic method with out parameter in c#. it will return two out parameter value which I pass list object .
public void ExecuteList<T, T1>(out List<T> obj, out List<T1> obj1, string sql, params object[] parameters) where T : class
{
using (var db = _context)
{
var cmd = db.Database.Connection.CreateCommand();
cmd.CommandText = sql;
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddRange(parameters);
try
{
db.Database.Connection.Open();
using (var reder = cmd.ExecuteReader())
{
obj = ((IObjectContextAdapter)db).ObjectContext.Translate<T>(reder).ToList();
reder.NextResult();
obj1 = ((IObjectContextAdapter)db).ObjectContext.Translate<T1>(reder).ToList();
}
}
finally
{
db.Database.Connection.Close();
cmd.Dispose();
}
}
}
Call this method
List<SqlParameter> parameterList = new List<SqlParameter>();
parameterList.Add(new SqlParameter("#pageNo", 1));
parameterList.Add(new SqlParameter("#pageSize", 5));
SqlParameter[] parameters = parameterList.ToArray();
List<PostModel> PostList = new List<PostModel>();
List<Tag> TagList = new List<Tag>();
Uow.ExecuteList<PostModel,Tag>(out PostList, out TagList, "[dbo].[sp_getdata]", parameters);
Here I pass postmodel and tag class for casting and also pass two out parameter PostList and TagList for result.
It will return perfect result.
But my requirement is these casting classes and out parameters should be optional.
Like this:
When I want one Result then pass one casting class and one Out parameter.
List<SqlParameter> parameterList = new List<SqlParameter>();
parameterList.Add(new SqlParameter("#pageNo", 1));
parameterList.Add(new SqlParameter("#pageSize", 5));
SqlParameter[] parameters = parameterList.ToArray();
List<PostModel> PostList = new List<PostModel>();
Uow.ExecuteList<PostModel>(out PostList, "[dbo].[sp_getdata]", parameters);
And when I want two Result then pass two casting class and two out parameter.
List<SqlParameter> parameterList = new List<SqlParameter>();
parameterList.Add(new SqlParameter("#pageNo", 1));
parameterList.Add(new SqlParameter("#pageSize", 5));
SqlParameter[] parameters = parameterList.ToArray();
List<PostModel> PostList = new List<PostModel>();
List<Tag> TagList = new List<Tag>();
Uow.ExecuteList<PostModel,Tag>(out PostList, out TagList, "[dbo].[sp_getdata]", parameters);
Please help me to solve my issue
You can create several overloads which will call the same private method:
private void Execute<T, T1>(ref List<T> obj, ref List<T1> obj1, string sql, params object[] parameters) where T : class
{
using (var db = _context)
{
var cmd = db.Database.Connection.CreateCommand();
cmd.CommandText = sql;
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.AddRange(parameters);
try
{
db.Database.Connection.Open();
using (var reader = cmd.ExecuteReader())
{
obj = ((IObjectContextAdapter)db).ObjectContext.Translate<T>(reader).ToList();
if(obj1 != null) {
reader.NextResult();
obj1 = ((IObjectContextAdapter)db).ObjectContext.Translate<T1>(reader).ToList();
}
}
}
finally
{
db.Database.Connection.Close();
cmd.Dispose();
}
}
}
public void ExecuteList<T, T1>(out List<T> obj, out List<T1> obj1, string sql, params object[] parameters) where T : class
{
obj = new List<T>();
obj1 = new List<T1>();
Execute(ref obj, ref obj1, sql, parameters);
}
public void ExecuteList<T>(out List<T> obj, string sql, params object[] parameters) where T : class
{
obj = new List<T>();
List<object> stub = null;//generic argument doesn't matter because it will not be used
Execute<T, object>(ref obj, ref stub, sql, parameters);
}
Note that my first method is private and should be called only from public overloads(it's possible to add as many output parameters/overloads as you wish).
Also probably it makes sense to restrict T1 to class as well and use List<SqlParameter> instead of params object[] parameters as a last parameter.
I was trying to implement a wrapper in C# for SQL Server.
The normal workflow without wrapper is fetching the data into a datatable using direct SQL query and then mapping the columns by names into entities.
But as a wrapper is better to accept a mapping function which describes which column maps to which fields of an enumerable.
So, something like this :
public class UserInfo
{
public string FirstName{ get; set; }
public string LastName{ get; set; }
}
enumerableList = dbManager.Execute("** sql query **", /* some method to specify mapping */);
The enumerable will then contain the result from the database, mapped by the execute method. But I am unsure how to specify the mapping?
Even if I do then how to deal with the different data types for each column in the mapping?
If I correct understand, you want something like this:
public static List<T> ReadRows<T>(this SqlHelper sql, string query, SqlParameter[]
parameters, Func<SqlDataReader, T> projection)
{
var command = GetSqlCommand(query, CommandType.StoredProcedure, parameters);
return sql.ExecuteReader(command, reader => reader.Select(projection).ToList());
}
And use like:
var members = _unitOfWork.SqlHelper.ReadRows("spGetMembersByUserCompanies", parameters, _memberProjection);
readonly Func<SqlDataReader, MemberVm> _memberProjection = (r) => new MemberVm
{
InvitationId = r.Get<int?>("InvitationId"),
UserName = r.Get<string>("UserName"),
RoleName = r.Get<string>("RoleName"),
InvitationStatus = (InvitationStatus)r.Get<int>("InvitationStatus"),
LogoUrl = r.Get<string>("LogoUrl")
};
It is a piece of my code. I hope it is start to resolve your problem.
Implementing such a wrapper from bare bones is not that easy. But it is possible. There is an ADO wrapper library in Github : ADOWrapper
The implementation is pretty straightforward.
Short Answer
How to specify mapping between columns? - Use Func
How to deal with the different data types? You can write an extension
Long Answer
Make a generic method that takes as input the query and a Func Delegate (and optional third parameter to pass query parameters as dictionary)
public ICollection<T> Execute<T>(string query, Func<IDataReader, T> map, IDictionary<string, object> parameters = null)
{
ICollection<T> collection = new List<T>();
using (SqlConnection connection = CreateConnection())
{
connection.Open();
using (SqlCommand command = CreateCommand(connection, query, parameters))
{
using (IDataReader reader = await command.ExecuteReader())
{
while(reader.Read())
{
collection.Add(map.Invoke(reader));
}
}
}
connection.Close();
}
return collection;
}
Implementation of AddParameter AND CreateCommand:
private void AddParameter(IDbCommand command, string parameter, object value)
{
IDbDataParameter param = command.CreateParameter();
param.ParameterName = parameter;
param.Value = value;
command.Parameters.Add(param);
}
private SqlCommand CreateCommand(SqlConnection connection, string query,
IDictionary<string, object> parameters = null)
{
SqlCommand command = connection.CreateCommand();
command.CommandText = query;
if(parameters != null && parameters.Count > 0)
{
foreach(KeyValuePair<string, object> parameter in parameters)
{
AddParameter(command, parameter.Key, parameter.Value);
}
}
return command;
}
You can call the method like this :
public class UserInfo
{
public string FirstName{ get; set; }
public string LastName{ get; set; }
}
var enumerableList = manager.Execute("** query **",
(reader) =>
{
return new UserInfo()
{
FirstName = reader.Get<string>("FirstName"),
LastName = reader.Get<string>("LastName "),
};
})
The Get method makes it easy to manage different data types being fetched from column. But it is not an inbuilt method. So you need to write an extension for Data Reader:
public static class DataReaderExtension
{
public static T Get<T>(this IDataReader reader, string column) where T : IComparable
{
try
{
int index = reader.GetOrdinal(column);
if (!reader.IsDBNull(index))
{
return (T)reader[index];
}
}
catch (IndexOutOfRangeException) { throw new Exception($"Column, '{column}' not found."); }
return default(T);
}
public static IEnumerable<string> GetColumns(this IDataReader reader)
{
IEnumerable<string> columns = new List<string>();
if (reader != null && reader.FieldCount > 0)
{
columns = Enumerable.Range(0, reader.FieldCount)
.Select(index => reader.GetName(index))
.ToList();
}
return columns;
}
}
I have this static class:
namespace Dapper
{
public static class SqlMapper
{
public static T ExecuteScalar<T>(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
CommandDefinition command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, new CancellationToken());
return SqlMapper.ExecuteScalarImpl<T>(cnn, ref command);
}
public static Task<int> ExecuteAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null)
{
return cnn.ExecuteAsync(new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, new CancellationToken()));
}
//...
//Goes on for another 200 different static functions
}
}
I want to create some wrapper class that will hold a default value of commandTimeout. I don't want it as a global parameter, I want this class to be build in bootstrapper with this value:
using Dapper;
public class SqlWrapper : ISqlWrapper
{
private readonly ILogger _logger;
private readonly int _commandTimeoutInSec;
public SqlWrapper(ILogger logger, int commandTimeoutInSec)
{
_logger = logger;
_commandTimeoutInSec = commandTimeoutInSec;
}
public async ExecuteScalarAsync<T>(IDbConnection cnn, string sql, object param = null,
int? commandTimeout = null, CommandType? commandType = null)
{
try
{
using (var conn = new SqlConnection(cnn))
{
var commandGuid =
await conn.ExecuteScalarAsync<T>(sql, param, CommandType: CommandType, commandTimeout: commandTimeout ?? _commandTimeoutInSec);
return commandGuid;
}
}
catch (Exception ex)
{
_logger.WriteError(
$"Job execute failed with error:", ex);
throw;
}
}
public async int ExecuteAsync(IDbConnection cnn, string sql, object param = null, int? commandTimeout = null, CommandType? commandType = null)
{
try
{
using (var conn = new SqlConnection(cnn))
{
var commandGuid =
await
conn.ExecuteAsync(sql, param, CommandType: CommandType,
commandTimeout: commandTimeout ?? _commandTimeoutInSec);
return commandGuid;
}
}
catch (Exception ex)
{
_logger.WriteError(
$"Job execute failed with error:", ex);
throw;
}
}
//...
//Goes on for the rest 200 different static functions in SqlMapper
}
the thing is, I feel like it's stupid to implement a wrapper for 200 functions to to pass a default parameter, is there a way to receive the name of the function as a template of parameter and then pass on the call to the same name in SqlMapper?
You may apply the following technique to your scenario. Imagine we had this static class:
public static class Printer
{
public static void Print(string output, int numberOfTimes)
{
for (int i = 0; i < numberOfTimes; i++)
{
Console.WriteLine(output);
}
}
public static void Show(string output, int numberOfTimes)
{
for (int i = 0; i < numberOfTimes; i++)
{
Console.WriteLine(output);
}
}
}
And let's imagine we wanted to give the numberOfTimes a default value, then we can do this:
public class DefaultPrinter
{
private int defaultTimes = 10;
public void ExecuteMethod(Action<string, int> action, string output)
{
action(output, defaultTimes);
}
}
Now the users will not have to indicate how many times the print should occur because the above class will do it for a default of 10 times.
Usage
var printer = new DefaultPrinter();
printer.ExecuteMethod((a, b) => Printer.Print(a, b), "One");
printer.ExecuteMethod((a, b) => Printer.Show(a, b), "Two");
I have the following, it produces an error saying I need to specify the parameter SearchCriteria which is the only parameter of the stored procedure, am I missing something here?
public async Task<List<T>> SqlQueryAsync<T>(string sql, params object[] parameters) where T : class
{
return await _context.Database.SqlQuery<T>(sql, parameters).ToListAsync();
}
The calling code has this
object[] args = new object[] { searchQuery };
var list = await _repository.SqlQueryAsync<Landlord>(StoredProcedures.LandlordSearch, args);
public static readonly string LandlordSearch = "LandlordSearch #SearchCriteria";
I have this class, in which I am wrapping dapper calls in order to do something like
var results = SqlWrapper.ExecuteQuery<Product,Customer>("SELECT id FROM Products; SELECT id FROM Customers;");
Where
results[0] = List<Product>
results[1] = List<Customer>
I support 1,2,3 output objects, but would like arbitrary. The class is also ugly and full of copy and pasted code. I account for if I want to reuse a connection by optionally passing a connection but the code just seems unclean. What I would really like is a way to define params T[] but as I understand that doesnt work. Is this any way this code can be cleaned/shortened?
using System.Collections.Generic;
using System.Data.SqlClient;
using System.Linq;
using Dapper;
namespace SqlWrapper
{
public static class SqlWrapper
{
private const string SqlConnectionString = "Server=localhost;Database=TTDS;User Id=sa;Password=sa;";
public static List<T> ExecuteQuery<T>(string sql, object param = null, SqlConnection sqlConnection = null)
{
if (sqlConnection != null)
{
return sqlConnection.Query<T>(sql, param).ToList();
}
using (var tempSqlConnection = new SqlConnection(SqlConnectionString))
{
tempSqlConnection.Open();
return tempSqlConnection.Query<T>(sql, param).ToList();
}
}
public static List<dynamic> ExecuteQuery<T1, T2>(string sql, object param = null, SqlConnection sqlConnection = null)
{
if (sqlConnection != null)
{
return MultiQuery<T1, T2>(sqlConnection, sql, param);
}
using (var tempSqlConnection = new SqlConnection(SqlConnectionString))
{
return MultiQuery<T1, T2>(tempSqlConnection, sql, param);
}
}
public static List<dynamic> ExecuteQuery<T1, T2, T3>(string sql, object param = null,
SqlConnection sqlConnection = null)
{
if (sqlConnection != null)
{
return MultiQuery<T1, T2, T3>(sqlConnection, sql, param);
}
using (var tempSqlConnection = new SqlConnection(SqlConnectionString))
{
return MultiQuery<T1, T2, T3>(tempSqlConnection, sql, param);
}
}
private static List<dynamic> MultiQuery<T1, T2>(SqlConnection sqlConnection, string sql, object param = null)
{
var rv = new List<dynamic>();
using (var grid = sqlConnection.QueryMultiple(sql, param))
{
rv.Add(grid.Read<T1>().ToList());
rv.Add(grid.Read<T2>().ToList());
}
return rv;
}
private static List<dynamic> MultiQuery<T1, T2, T3>(SqlConnection sqlConnection, string sql, object param = null)
{
var rv = new List<dynamic>();
using (var grid = sqlConnection.QueryMultiple(sql, param))
{
rv.Add(grid.Read<T1>().ToList());
rv.Add(grid.Read<T2>().ToList());
rv.Add(grid.Read<T3>().ToList());
}
return rv;
}
public static void ExecuteNonQuery(SqlConnection sqlConnection, string sql, object param, int? timeout = null)
{
if (sqlConnection != null)
{
sqlConnection.Execute(sql, param, commandTimeout: timeout);
}
else
{
using (var tempSqlConnection = new SqlConnection(SqlConnectionString))
{
tempSqlConnection.Open();
tempSqlConnection.Execute(sql, param, commandTimeout: timeout);
}
}
}
}
}
Here is some untested code that demonstrates a couple of ideas that I had.
While "using" is pretty awesome, you can pare down your code some if you optionally create the connection first and then, if necessary, dispose the sqlConnection in a finally block.
If you return a Tuple<List<T>,List<U>,List<V>> you can have strongly typed return values that you can easily use
If you call your most complex function from those that are less complex, you can minimize your duplicated code.
public static class SqlWrapper
{
private const string SqlConnectionString = "Server=localhost;Database=TTDS;User Id=sa;Password=sa;";
private class NoResult { }
public static List<T1> ExecuteQuery<T1>(string sql, object param = null, SqlConnection sqlConnection = null)
{
return ExecuteQuery<T1, NoResult, NoResult>(sql, param, sqlConnection).Item1;
}
public static Tuple<List<T1>, List<T2>> ExecuteQuery<T1, T2>(string sql, object param = null, SqlConnection sqlConnection = null)
{
var result = ExecuteQuery<T1, T2, NoResult>(sql, param, sqlConnection);
return Tuple.Create(result.Item1, result.Item2);
}
public static Tuple<List<T1>, List<T2>, List<T3>> ExecuteQuery<T1, T2, T3>(string sql, object param = null, SqlConnection sqlConnection = null)
{
List<T1> list1;
List<T2> list2 = null;
List<T3> list3 = null;
bool needsDisposed = false;
if (sqlConnection == null)
{
sqlConnection = new SqlConnection(SqlConnectionString);
sqlConnection.Open();
needsDisposed = true;
}
try
{
using (var grid = sqlConnection.QueryMultiple(sql, param))
{
list1 = grid.Read<T1>().ToList();
if (typeof(T2) != typeof(NoResult))
{
list2 = grid.Read<T2>().ToList();
}
if (typeof(T3) != typeof(NoResult))
{
list3 = grid.Read<T3>().ToList();
}
return Tuple.Create(list1, list2, list3);
}
}
finally { if (needsDisposed) sqlConnection.Dispose(); }
}
public static void ExecuteNonQuery(SqlConnection sqlConnection, string sql, object param, int? timeout = null)
{
bool needsDisposed = false;
if (sqlConnection == null)
{
sqlConnection = new SqlConnection(SqlConnectionString);
sqlConnection.Open();
needsDisposed = true;
}
try { sqlConnection.Execute(sql, param, commandTimeout: timeout); }
finally { if (needsDisposed) sqlConnection.Dispose(); }
}
}