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(); }
}
}
Related
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'm creating a generic application to get all the data from different SQL Server tables.
I have a generic function that convert the SqlDataReader to a list:
public static List<T> MapToList<T>(this SqlDataReader dr) where T : new()
{
List<T> RetVal = null;
var Entity = typeof(T);
var PropDict = new Dictionary<string, PropertyInfo>();
try
{
if (dr != null && dr.HasRows)
{
RetVal = new List<T>();
var Props = Entity.GetProperties(BindingFlags.Instance | BindingFlags.Public);
PropDict = Props.ToDictionary(p => p.Name.ToUpper(), p => p);
while (dr.Read())
{
T newObject = new T();
for (int Index = 0; Index < dr.FieldCount; Index++)
{
if (PropDict.ContainsKey(dr.GetName(Index).ToUpper()))
{
var Info = PropDict[dr.GetName(Index).ToUpper()];
if ((Info != null) && Info.CanWrite)
{
var Val = dr.GetValue(Index);
Info.SetValue(newObject, (Val == DBNull.Value) ? null : Val, null);
}
}
}
RetVal.Add(newObject);
}
}
}
catch (Exception)
{
throw;
}
return RetVal;
}
Now suppose I have this class for my data:
public partial class User
{
public int Id { get; set; }
public string Name { get; set; }
}
I can fetch my data from the table something like this:
const string GetAreasQuery = "select id, name from dbo.user";
SqlDataReader dr = DoQueryToDB(GetAreasQuery);
List<User> userList = dr.MapToList<User>();
Now, I have n different classes like User (Classroom and so on) and I don't want to write the code above for each class I have. I would like to create a generic GetData to retrieve those information:
public List<T> GetData<T> (string Query_)
{
SqlDataReader dr = DataReader(Query_);
List<T> data = new List<T>();
data = dr.MapToList<T>();
return data;
}
where T can be User, Classroom and so on...
I tried this solution but I always have to specify the type:
public object GetData(string Query_, Type type)
{
SqlDataReader dr = DataReader(Query_);
if (type == typeof(User))
{
List<User> data = new List<User>();
data = dr.MapToList<User>();
return data;
}
else if (..)
{}
return null;
}
I'm trying different possibilities but I always obtain an error in GetData<T> function. More precisely in MapToList<T> like: T should be a non abstract type or public constructor without parameters.
You should add a contraint to the method GetData in order to achieve the same constraint level that is found in MapToList, which requires T to have a empty constructor
public List<T> GetData<T>(string Query_) where T : new()
{
SqlDataReader dr = DataReader(Query_);
return dr.MapToList<T>();
}
I'm writing an SQL Data Mapper that would map queries to dynamic types (I've already experienced mapping sql to strongly typed objects through reflection)
This time I want to make a sql call then return an Ienumerable.
This is what I have so far:
public class DynamicMapper
{
private class ReaderColumnMap
{
public string Column { get; }
public Type Type { get; }
public ReaderColumnMap(string column, Type type)
{
Column = column;
Type = type;
}
}
private Func<IDataReader, dynamic> MappingMethod;
public DynamicMapper(IDataReader schemaSource)
{
MappingMethod = CreateMapper(schemaSource);
}
private Func<IDataReader, dynamic> CreateMapper(IDataReader reader)
{
IDictionary<int, ReaderColumnMap> propertyMappingsCache = new Dictionary<int, ReaderColumnMap>();
//The schema of sql table involves the column name and data type for each columns.
DataTable schema = reader.GetSchemaTable();
DataColumnCollection dataColumns = schema.Columns;
//Gets the column name ordinal and data type ordinal
int nameColumn = dataColumns["ColumnName"].Ordinal;
int typeColumn = dataColumns["DataType"].Ordinal;
int ordinalColumn = dataColumns["ColumnOrdinal"].Ordinal;
DataRowCollection schemaRows = schema.Rows;
int schemaRowCount = schemaRows.Count - 1;
//Populates the property mappings
for (int i = schemaRowCount; i != -1; --i)
{
DataRow row = schemaRows[i];
string name = row[nameColumn].ToString();
Type type = Type.GetType(row[typeColumn].ToString());
int ordinal = Convert.ToInt32(row[ordinalColumn]);
propertyMappingsCache.Add(ordinal, new ReaderColumnMap(name, type));
}
//Use expression tree here to create dynamic object (new() { Prop1 = "prop1", Prop2 = prop2... })
//Dictionary sample:
//Id = System.Int64
//Name = System.String
//Birthdate = System.DateTime
//Target Expression:
//dynamic instance = new (){
// Id = (System.Int64)reader[0];
// Name = (System.String)reader[1];
// Birthdate = (System.DateTime)reader[2];
//}
var expressions = new List<Expression>();
var fxParameterExpression = Expression.Parameter(typeof(IDataReader), "reader");
//Compiler error: the typeof operator cannot be used on the dynamic type
var variableExpression = Expression.Variable(typeof(dynamic), "instance"); //dynamic instance = new ()
//Compiler error: the typeof operator cannot be used on the dynamic type
var instantiationExpression = Expression.New(typeof(dynamic));
var assignExpression = Expression.Assign(variableExpression, instantiationExpression);
var indexer = typeof(IDataReader).GetProperty("Item", new[] { typeof(int) }); // []
expressions.Add(assignExpression);
int propertyCount = propertyMappingsCache.Count - 1;
for(int i = propertyCount; i != -1; --i)
{
//Get property details from mappingsCache (string property name and Type of property)
//to create property
}
return null;
}
public dynamic CreateMappedInstance(IDataReader reader)
{
return MappingMethod.Invoke(reader);
}
}
But the compiler is not letting me to instantiate dynamic type: the typeof operator cannot be used on the dynamic type
I plan to call it in sql module like:
public class SqlCaller
{
private readonly ISqlProvider sqlProvider;
public SqlCaller(ISqlProvider sqlProvider)
{
this.sqlProvider = sqlProvider ?? throw new ArgumentNullException("sqlProvider");
}
public DataTable Query(string queryString)...
public DataTable Query(DbCommand command)...
public DataTable GetSchema(string queryString)...
public int ExecuteNonQuery(DbCommand command)...
public int ExecuteNonQuery(string commandString)...
public object ExecuteScalar(string queryString)...
public object ExecuteScalar(DbCommand command)...
public bool ExecuteTransaction(IEnumerable<DbCommand> commands)...
[Obsolete]
public IEnumerable<T> Get<T>(Func<IDataReader, List<T>> mappingMethod, string query)...
[Obsolete]
public IEnumerable<T> Get<T>(Func<IDataReader, List<T>> mappingMethod, DbCommand command)...
public IEnumerable<T> Get<T>(string query) where T : class, new()...
public IEnumerable<T> Get<T>(DbCommand command) where T : class, new()...
public IEnumerable<T> Get<T>(IDataMapper<T> dataMapper, DbCommand command) where T : class, new()...
public IEnumerable<dynamic> GetDynamic(string commandString)
{
return GetDynamic(sqlProvider.CreateCommand(commandString));
}
public IEnumerable<dynamic> GetDynamic(DbCommand command)
{
List<dynamic> temp = new List<dynamic>();
using (DbConnection connection = sqlProvider.CreateConnection())
{
command.Connection = connection;
try
{
command.Connection.Open();
IDataReader reader = command.ExecuteReader();
var watch = Stopwatch.StartNew();
DynamicMapper mapper = new DynamicMapper(reader);
while(reader.Read()) temp.Add(mapper.CreateMappedInstance(reader));
watch.Stop();
}
catch
{
throw;
}
finally
{
command.Connection.Close();
}
}
return temp;
}
}
How to instantiate a dynamic type inside an expression tree
How to set property values of a dynamic type inside an expression tree
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'm using a IDbcontext to acces my database
public sealed class DbContext : IDbContext
{
private bool disposed;
private SqlConnection connection;
public DbContext(string connectionString)
{
connection = new SqlConnection(connectionString);
}
public IDbConnection Connection
{
get
{
if (disposed) throw new ObjectDisposedException(GetType().Name);
return connection;
}
}
public IDbTransaction CreateOpenedTransaction()
{
if (connection.State != ConnectionState.Open)
Connection.Open();
return Connection.BeginTransaction();
}
public IEnumerable<T> ExecuteProcedure<T>(string procedure, dynamic param = null, IDbTransaction transaction = null)
{
if (connection.State == ConnectionState.Closed)
{
connection.Open();
}
return Dapper.SqlMapper.Query<T>(connection, procedure, param, transaction,
commandType: CommandType.StoredProcedure);
}
public int ExecuteProcedure(string procedure, dynamic param = null, IDbTransaction transaction = null)
{
if (connection.State == ConnectionState.Closed)
{
connection.Open();
}
return Dapper.SqlMapper.Execute(connection, procedure, param, transaction,
commandType: CommandType.StoredProcedure);
}
public void Dispose()
{
Debug.WriteLine("** Disposing DbContext");
if (disposed) return;
if (connection != null)
{
connection.Dispose();
connection = null;
}
disposed = true;
}
}
I have 2 database "first" and "second"
In the resolution o depenancy of fist, i use
builder.Register<IDbContext>(c =>
new DbContext(ConfigurationManager.ConnectionStrings["first"].ConnectionString))
.InstancePerDependency();
So i need to add:
builder.Register<IDbContext>(c =>
new DbContext(ConfigurationManager.ConnectionStrings["second"].ConnectionString))
.InstancePerDependency();
I need to create a factory for Dbcontext and Use NamedResolve for with autofac, How can I do this??
public class DbContextFactory
{
private ILifetimeScope m_RootLifetimeScope;
public DbContextFactory(ILifetimeScope rootLifetimeScope)
{
m_RootLifetimeScope = rootLifetimeScope;
}
public IDbContext CreateDbContext()
{
if (logic for selection first dbcontext)
{
return m_RootLifetimeScope.ResolveNamed<IDbContext>("first");
}
else if (logic for selection second dbcontext)
{
return m_RootLifetimeScope.ResolveNamed<IDbContext>("second");
}
else
{
throw new NotSupportedException();
}
}
}
//registration
builder.RegisterType<DbContextFactory>().SingleInstance();
//using
var factory = yourContainer.Resolve<DbContextFactory>();
var context = factory.CreateDbContext();