how to create a generic method with optional generic parameter in c# - c#

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.

Related

cast system.object to List<model> asp.net mvc

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
});
}

Implementing an SQL wrapper with entity mapping

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;
}
}

How do I pass a table-valued parameter to Dapper in .NET Core?

I am using .NET Core and Dapper. My problem is that .NET Core doesn't have DataTables, and that's what Dapper uses for table-valued parameters (TVP).
I was trying to convert a List<T> to a List<SqlDataRecord>, create a SqlParameter with this list and then convert it to a DynamicParameter:
public static SqlParameter toTVP<T>(this IList<T> enumerable, string name)
{
List<SqlDataRecord> records = new List<SqlDataRecord>();
// filter allowed types
var properties = typeof(T).GetProperties().Where(p => Mapper.TypeToSQLMap.ContainsKey(p.PropertyType));
var definitions = properties.Select(p => Mapper.TypeToMetaData(p.Name,p.PropertyType)).ToArray();
foreach(var item in enumerable)
{
var values = properties.Select(p => p.GetValue(item, null)).ToArray();
var schema = new SqlDataRecord(definitions);
schema.SetValues(values);
records.Add(schema);
}
SqlParameter result = new SqlParameter(name, SqlDbType.Structured);
result.Direction = ParameterDirection.Input;
result.TypeName = $"{name}Type";
result.Value = records;
return result;
}
and then:
var structured = MyList.toTVP("Test");
var p = new DynamicParameters(new { });
p.Add(structured.ParameterName,structured.Value);
var result = con.Query(query, p);
But I got an error:
The member of type Microsoft.SqlServer.Server.SqlDataRecord cannot be used as a parameter value.
Is there a way I can make this work?
As of Dapper 2.0, TVPs are natively supported. There's some sample code is available on GitHub:
https://github.com/yorek/dapper-samples/blob/master/Dapper.Samples.Advanced/SQLServerFeatures.cs
For pre-2.0 TVPs, you would need to use .NET Framework, where you can use the .AsTableValuedParameter extension methods, but you don't have this option in .NET Core (as of Dapper v 1.5). To solve the problem you have to create a class that implements ICustomQueryMapper:
public class TabledValuedParameter: ICustomQueryMapper
{
public void AddParameter() {...}
}
And then you can use it to wrap your IEnumerable. I've written an article on the subject here:
https://medium.com/dapper-net/sql-server-specific-features-2773d894a6ae
Asker's solution moved to an answer:
After playing a bit with IDynamicParameters, I made it work.
Extension method for IEnumerable
public static DynamicWrapper toTVP<T>(this IEnumerable<T> enumerable, string tableName, string typeName)
{
List<SqlDataRecord> records = new List<SqlDataRecord>();
var properties = typeof(T).GetProperties().Where(p => Mapper.TypeToSQLMap.ContainsKey(p.PropertyType));
var definitions = properties.Select(p => Mapper.TypeToMetaData(p.Name, p.PropertyType)).ToArray();
foreach (var item in enumerable)
{
var values = properties.Select(p => p.GetValue(item, null)).ToArray();
var schema = new SqlDataRecord(definitions);
schema.SetValues(values);
records.Add(schema);
}
SqlParameter result = new SqlParameter(tableName, SqlDbType.Structured);
result.Direction = ParameterDirection.Input;
result.TypeName = typeName;
result.Value = records;
return new DynamicWrapper(result);
}
Wrapper to implement IDynamicParameters
public class DynamicWrapper : IDynamicParameters
{
private readonly SqlParameter _Parameter;
public DynamicWrapper(SqlParameter param)
{
_Parameter = param;
}
public void AddParameters(IDbCommand command, Identity identity)
{
command.Parameters.Add(_Parameter);
}
}
Mapper (not fully tested, only managed string to NVARCHAR because it
throws an exception without maxLength)
public class Mapper
{
public static Dictionary<Type, SqlDbType> TypeToSQLMap = new Dictionary<Type, SqlDbType>()
{
{typeof (long),SqlDbType.BigInt},
{typeof (long?),SqlDbType.BigInt},
{typeof (byte[]),SqlDbType.Image},
{typeof (bool),SqlDbType.Bit},
{typeof (bool?),SqlDbType.Bit},
{typeof (string),SqlDbType.NVarChar},
{typeof (DateTime),SqlDbType.DateTime2},
{typeof (DateTime?),SqlDbType.DateTime2},
{typeof (decimal),SqlDbType.Money},
{typeof (decimal?),SqlDbType.Money},
{typeof (double),SqlDbType.Float},
{typeof (double?),SqlDbType.Float},
{typeof (int),SqlDbType.Int},
{typeof (int?),SqlDbType.Int},
{typeof (float),SqlDbType.Real},
{typeof (float?),SqlDbType.Real},
{typeof (Guid),SqlDbType.UniqueIdentifier},
{typeof (Guid?),SqlDbType.UniqueIdentifier},
{typeof (short),SqlDbType.SmallInt},
{typeof (short?),SqlDbType.SmallInt},
{typeof (byte),SqlDbType.TinyInt},
{typeof (byte?),SqlDbType.TinyInt},
{typeof (object),SqlDbType.Variant},
{typeof (DataTable),SqlDbType.Structured},
{typeof (DateTimeOffset),SqlDbType.DateTimeOffset}
};
public static SqlMetaData TypeToMetaData(string name, Type type)
{
SqlMetaData data = null;
if (type == typeof(string))
{
data = new SqlMetaData(name, SqlDbType.NVarChar, -1);
}
else
{
data = new SqlMetaData(name, TypeToSQLMap[type]);
}
return data;
}
}
SQL Type for my example:
CREATE TYPE TestType AS TABLE (
FirstName NVARCHAR(255)
, GamerID INT
, LastName NVARCHAR(255)
, Salt UNIQUEIDENTIFIER);
GO
Using it:
List<Gamer> gamers = new List<Gamer>();
gamers.Add(new Gamer {
Email = new string[] { "dsadsdsa#dasddas.com" },
FirstName = "Test_F0",
LastName = "Test_L0",
GamerID = 0,
Salt = Guid.NewGuid()});
gamers.Add(new Gamer {
Email = new string[] { "11111#11111.com" },
FirstName = "Test_F1",
LastName = "Test_L1",
GamerID = 1,
Salt = Guid.NewGuid()});
var structured = gamers.toTVP("GamerTable", "dbo.TestType");
using (var con = new SqlConnection(TestConnectionString))
{
con.Open();
string query = #"
SELECT *
FROM #GamerTable t
WHERE t.GamerID = 1
";
var result = con.Query(query, structured);
//var result = con.Query("dbo.DapperTest", structured, commandType: CommandType.StoredProcedure);
As you can see, the model stripped out the array of strings for
emails, coz I didn't code it to have nested tvp.
(TypeToSQLMap.ContainsKey part), but could be coded, changing the
wrapper to accept an enumerable of parameters and AddParameters to
foreach and add them. Is more about a problem with the types names,
etc. I was thinking to create some generic types named based on the
property types. For now, this is enough, feel free to upgrade it if i
dont do it.

Entity Framework Core - Mapping Raw Query(DTO Class without ID field) , Scalar

I am using EFCore with ASPNETCore, everything looks great, but I find these two situations:
I have created a DTO classes that are not tables in the database, these classes that will map a raw SQL query or procedure.
So I need to map the results of an SQL procedure in a list classs DTO, the question I have is if EFCore requires that all classes of DTO have a field named ID?, for more than my procedure does not necessarily have that field ?, there a way to specify to EFCore that my query need not map any field named ID?.
Likewise, what it is the correct way to map a scalar value or a unique result of a raw SQL query or procedure?
Thank you very much for your support.
Here is a class that will solve your needs.
Usage var listOfEntity = DatabaseServices.ExecuteStoredProcedure(Stored Procedure Name, List of SqlParameter)
public class DatabaseServices : IDatabaseServices
{
private readonly ApplicationDbContext _applicationDbContext;
public DatabaseServices(ApplicationDbContext applicationDbContext)
{
_applicationDbContext = applicationDbContext;
}
public List<T> ExecuteStoreProcedure<T>(string storedProcedure, List<SqlParameter> parameters) where T : new()
{
using (var cmd = _applicationDbContext.Database.GetDbConnection().CreateCommand())
{
cmd.CommandText = storedProcedure;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandTimeout = 300;
// set some parameters of the stored procedure
foreach (var parameter in parameters)
{
parameter.Value = parameter.Value ?? DBNull.Value;
cmd.Parameters.Add(parameter);
}
if (cmd.Connection.State != ConnectionState.Open)
cmd.Connection.Open();
using (var dataReader = cmd.ExecuteReader())
{
var test = DataReaderMapToList<T>(dataReader);
return test;
}
}
}
private static List<T> DataReaderMapToList<T>(DbDataReader dr)
{
List<T> list = new List<T>();
if (dr.HasRows)
{
while (dr.Read())
{
var obj = Activator.CreateInstance<T>();
foreach (PropertyInfo prop in obj.GetType().GetProperties())
{
if (!Equals(dr[prop.Name], DBNull.Value))
{
prop.SetValue(obj, dr[prop.Name], null);
}
}
list.Add(obj);
}
return list;
}
return new List<T>();
}
}

Wrapping multiple queries with multiple outputs

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(); }
}
}

Categories

Resources