Refactoring two very similar overloads - c#

I have an overload:
public DataTable ExecuteStoredProcedure(string storedProcedure)
{
var dataTable = new DataTable();
using (var odbcConnection = _connection)
{
using (var odbcCommand = odbcConnection.CreateCommand())
{
odbcCommand.CommandText = storedProcedure;
odbcCommand.CommandType = CommandType.StoredProcedure;
using (var adapter = new OdbcDataAdapter(odbcCommand))
{
adapter.Fill(dataTable);
}
}
}
return dataTable;
}
and another overload:
public DataTable ExecuteStoredProcedure(string storedProcedure, List<StoredProcedureParameters> storedProcedureParameters)
{
var dataTable = new DataTable();
using (var odbcConnection = _connection)
{
using (var odbcCommand = odbcConnection.CreateCommand())
{
odbcCommand.CommandText = storedProcedure;
foreach (var parameter in storedProcedureParameters)
{
odbcCommand.Parameters.Add("#" + parameter.ParameterName, parameter.ParameterType,
parameter.LengthOfParameter).Value = parameter.ParameterName;
}
odbcCommand.CommandType = CommandType.StoredProcedure;
using (var adapter = new OdbcDataAdapter(odbcCommand))
{
adapter.Fill(dataTable);
}
}
}
return dataTable;
}
Whose contents are very similar. So similar in fact, the only difference is this line here:
foreach (var parameter in storedProcedureParameters)
{
odbcCommand.Parameters.Add("#" + parameter.ParameterName, parameter.ParameterType,
parameter.LengthOfParameter).Value = parameter.ParameterName;
}
I have spent a few hours trying to refactor this guy because they are so similar. I have tried delegates, but it made the code more unreadable. I cannot combine the functionality of the two overloads because the other overloads logic occurs in the middle of the other. Does anyone have any ideas as to how to refactor this into one readable method?

Couple of things you could do:
You could always just use the one method, but with an optional List<StoredProcedureParameters>.
Like this: (I've renamed it to spParams for brevity)
public DataTable ExecuteStoredProcedure(string storedProcedure,
List<StoredProcedureParameters> spParams
= new List<StoredProcedureParameters>())
{
var dataTable = new DataTable();
using (var odbcConnection = _connection)
{
using (var odbcCommand = odbcConnection.CreateCommand())
{
odbcCommand.CommandText = storedProcedure;
foreach (var parameter in spParams)
{
odbcCommand.Parameters.Add("#" + parameter.ParameterName, parameter.ParameterType,
parameter.LengthOfParameter).Value = parameter.ParameterName;
}
odbcCommand.CommandType = CommandType.StoredProcedure;
using (var adapter = new OdbcDataAdapter(odbcCommand))
{
adapter.Fill(dataTable);
}
}
}
return dataTable;
}
This way, you can call the same method, and choose whether to pass in the List<StoredProcedureParameters> (or not).
Using the parameter with default value of spParams= new List<StoredProcedureParameters>() will mean that any existing code calling the original 1 parameter signature will still work, saving time on extra refactoring.
Plus, it means your existing foreach block will just iterate over an empty list.
OR
You could do similar as the above, just have the spParams value be null as default, and then do a null check, like this:
public DataTable ExecuteStoredProcedure(string storedProcedure,
List<StoredProcedureParameters> spParams = null)
{
//...
if (spParams != null) // Check if the spParams is null
{
foreach(var param in spParams)
{
// Loop in here, if not null
}
}
//...
}
Hope this helps :)

Two approaches -
You can have the first method pass in an empty list of parameters to the second method:
public DataTable ExecuteStoredProcedure(string storedProcedure)
{
return ExecuteStoredProcedure(storedProcedure, new List<StoredProcedureParameters>());
}
You can pass null from the first method and add a null check in the second method, like this:
public DataTable ExecuteStoredProcedure(string storedProcedure)
{
return ExecuteStoredProcedure(storedProcedure, null);
}
...
if (storedProcedureParameters != null)
{
foreach (var parameter in storedProcedureParameters)
{
odbcCommand.Parameters.Add("#" + parameter.ParameterName, parameter.ParameterType, parameter.LengthOfParameter).Value = parameter.ParameterName;
}
}

You can add a flag to mark if you want to run the foreach or not. Of course there will be necessary to change the parameter name, and this is only one possible variant of refactor:
public DataTable ExecuteStoredProcedure(string storedProcedure, List<StoredProcedureParameters> storedProcedureParameters, bool withForEach)
{
var dataTable = new DataTable();
using (var odbcConnection = _connection)
{
using (var odbcCommand = odbcConnection.CreateCommand())
{
odbcCommand.CommandText = storedProcedure;
if(withForEach)
foreach (var parameter in storedProcedureParameters)
{
odbcCommand.Parameters.Add("#" + parameter.ParameterName, parameter.ParameterType,
parameter.LengthOfParameter).Value = parameter.ParameterName;
}
odbcCommand.CommandType = CommandType.StoredProcedure;
using (var adapter = new OdbcDataAdapter(odbcCommand))
{
adapter.Fill(dataTable);
}
}
}
return dataTable;
}

Why could you not do something like this...
public DataTable ExecuteStoredProcedure(string storedProcedure)
{
return ExecuteStoredProcedure(storedProcedure, null);
}
public DataTable ExecuteStoredProcedure(string storedProcedure, List<StoredProcedureParameters> storedProcedureParameters)
{
var dataTable = new DataTable();
using (var odbcConnection = _connection)
{
using (var odbcCommand = odbcConnection.CreateCommand())
{
odbcCommand.CommandText = storedProcedure;
if(storedProcedureParameters != null)
{
foreach (var parameter in storedProcedureParameters)
{
odbcCommand.Parameters.Add("#" + parameter.ParameterName, parameter.ParameterType,
parameter.LengthOfParameter).Value = parameter.ParameterName;
}
}
odbcCommand.CommandType = CommandType.StoredProcedure;
using (var adapter = new OdbcDataAdapter(odbcCommand))
{
adapter.Fill(dataTable);
}
}
}
return dataTable;
}

Related

C# - Converting DataReader to DataTable

I want to convert a DataReader to DataTable to display all customers in a list(demoClients) from the Database.
Currently I have this:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MySqlConnector;
using ProjectDatabase.Controllers;
using System.Data;
using System.Data.Common;
namespace ProjectDatabase.Models
{
public class demoClientsQuery
{
public AppDb Db { get; }
public demoClientsQuery(AppDb db)
{
Db = db;
}
public async Task<demoClients> FindAllClientsAsync(int id)
{
using var cmd = Db.conDemo.CreateCommand();
cmd.CommandText = #"Query";
cmd.Parameters.Add(new MySqlParameter
{
ParameterName = "#id_customer",
DbType = DbType.Int32,
Value = id,
});
MySqlDataReader dataReader = cmd.ExecuteReader();
DataTable dataTable = new DataTable();
dataTable.Load(dataReader);
var result = await ReadAllAsync(dataTable);
return result.Count > 0 ? result[0] : null;
}
public async Task<List<demoClients>> LatestClientsAsync()
{
using var cmd = Db.conDemo.CreateCommand();
cmd.CommandText = #"Query";
var aa = await LatestClientsAsync2();
MySqlDataReader dataReader = cmd.ExecuteReader();
DataTable dataTable = new DataTable();
dataTable.Load(dataReader);
return await ReadAllAsync(dataTable);
}
public async Task<DataTable> LatestClientsAsync2()
{
using var cmd = Db.conDemo.CreateCommand();
cmd.CommandText = #"Query";
//return await ReadAllAsync(await cmd.ExecuteReaderAsync());
return await ToDataTable(cmd);
}
private async Task<DataTable> ToDataTable(MySqlCommand cmd)
{
cmd.CommandType = CommandType.Text;
using (DbDataAdapter dataAdapter = new MySqlDataAdapter(cmd))
{
cmd.CommandType = CommandType.Text;
DataTable data = new DataTable();
dataAdapter.Fill(data);
return data;
}
}
private async Task<List<demoClients>> ReadAllAsync(DataTable dataTable)
{
var adb_demo_clients = new List<demoClients>();
using (dataTable)
{
foreach (DataRow dr in dataTable.Rows)
{
int id_customer = Convert.ToInt32(dr["id_customer"]);
string delivery_person_name = Convert.ToString(dr["delivery_person_name"]);
string firstname = Convert.ToString(dr["firstname"]);
string lastname = Convert.ToString(dr["lastname"]);
string email = Convert.ToString(dr["email"]);
}
}
return adb_demo_clients;
}
}
}
The only problem I am having is that it doesn't return nothing, it returns empty, something in my code is not returning my data from my database.
Any help would be appreciated.
Thank you for your time.
You have to add item to your adb_demo_clients list, inside the foreach, on the ReadAllAsync method:
private async Task<List<demoClients>> ReadAllAsync(DataTable dataTable)
{
var adb_demo_clients = new List<demoClients>();
using (dataTable)
{
foreach (DataRow dr in dataTable.Rows)
{
demoClients d = new demoClients();
d.id_customer = Convert.ToInt32(dr["id_customer"]);
d.delivery_person_name = Convert.ToString(dr["delivery_person_name"]);
// all fields that you need
...
// Add the item in your List
adb_demo_clients.add(d);
}
}
return adb_demo_clients;
}

Oracle error ORA-12571 encountered for Select Statement

I'm trying to read table data from ORACLE with Parameter binding in c#. While executing the dataAdapter I'm getting ORA-12571 exception.
Below is my code for reading multiple data at the same time.
public DataTable SelectFromServer(string qualifiedDBName, DataTable dataTable) {
try
{
if (this.Con.State == ConnectionState.Closed)
{
this.OpenConnection();
}
DataTable resultTable = new DataTable();
dataTable.TableName = qualifiedDBName;
DbProviderFactory factory = DbProviderFactories.GetFactory(this.Con);
using (DbCommand command = factory.CreateCommand())
{
command.Connection = this.Con;
command.CommandText = this.GenerateSqlToSelect(factory,command,dataTable);
DbDataAdapter adapter = factory.CreateDataAdapter();
adapter.SelectCommand = command;
adapter.Fill(resultTable);
return resultTable;
}
}
catch (Exception exc)
{
throw exc;
}
}
Below is the method where I used to generate Select Query
private string GenerateSqlToSelect(DbProviderFactory factory, DbCommand command, DataTable table)
{
//var values1 = new List<string>();
var SelectQuery = new StringBuilder();
var data = table.ToArray();
var syntax = new OracleSyntax();
command.GetType().GetProperty("ArrayBindCount").SetValue(command, table.Rows.Count, null);
for (var i = 0; i < table.Columns.Count; i++)
{
var names = new StringBuilder();
var values = new StringBuilder();
var column = table.Columns[i];
OracleParameter parameter = new OracleParameter();
parameter.ParameterName = column.ColumnName;
parameter.Direction = ParameterDirection.Input;
parameter.DbType = column.DataType.GetDbType();
parameter.Value = data[i];
parameter.ArrayBindSize = GetDataLength(data[i]);
if (SelectQuery.Length > 0)
{
SelectQuery.Append(" and ");
}
names.AppendFormat("{0}", column.ColumnName);
values.AppendFormat("{0}{1}", syntax.ParameterPrefix, column.ColumnName);
SelectQuery.AppendFormat("{0} = {1}", names, values);
command.Parameters.Add(parameter);
}
string operationString = "SELECT * FROM";
string sqlQuery = string.Format("{0} {1} WHERE {2}", operationString, this.FormatByQuote(syntax,table.TableName), SelectQuery);
return sqlQuery;
}
private int[] GetDataLength(object[] objs)
{
List<int> dataLengthIterator = new List<int>();
foreach (object obj in objs)
{
dataLengthIterator.Add(obj.ToString().Length);
}
return dataLengthIterator.ToArray();
}
this.FormatByQuote() method is nothing but getting a quoted TableName.
Output of GenerateSqlToSelect() method is
SELECT * FROM "CUSTOMER_MASTER" WHERE USER_ID= :USER_ID and LINE_NUMBER = :LINE_NUMBER
The Query is generating based upon the DataTable input
Tried multiple workarounds but could not get the reason why the exception is occurring.

SQL Server stored procedure complains about missing parameter, but it is being set

I have this method:
public class StoredProcedureProvider : IStoredProcedureProvider
{
private readonly string _connectionString;
public StoredProcedureProvider(IDataConfig config) => _connectionString = config.ConnectionString;
public T ExecuteScalar<T>(string procedureName, IList<SqlParameter> parameters = null)
{
return Execute(procedureName, parameters, (connection, command) =>
{
connection.Open();
var result = command.ExecuteScalar();
return (T) Convert.ChangeType(result, typeof(T));
});
}
public DataSet GetDataSet(string procedureName, IList<SqlParameter> parameters = null)
{
return Execute(procedureName, parameters, (connection, command) =>
{
var dataAdapter = new SqlDataAdapter();
var dataSet = new DataSet();
dataAdapter.SelectCommand = command;
dataAdapter.Fill(dataSet);
return dataSet;
});
}
public DataRow GetDataRow(string procedureName, IList<SqlParameter> parameters = null)
{
var dataSet = GetDataSet(procedureName, parameters);
var tables = dataSet.Tables.Cast<DataTable>().ToList();
if (!tables.Any())
throw new Exception("The DataSet contains not tables");
var dataRows = dataSet.Tables[0].Rows.Cast<DataRow>().ToList();
if (!dataRows.Any())
throw new Exception("The DataTable contains no rows");
var dataRow = dataRows.First();
return dataRow;
}
private static T Execute<T>(string procedureName, IList<SqlParameter> parameters, Func<SqlConnection, SqlCommand, T> returnMethod)
{
using (var connection = new SqlConnection(_connectionString))
{
using (var command = new SqlCommand(procedureName, connection))
{
command.CommandType = CommandType.StoredProcedure;
if (parameters == null || !parameters.Any())
return returnMethod(connection, command);
foreach (var parameter in parameters)
command.Parameters.Add(parameter);
return returnMethod(connection, command);
}
}
}
}
When I invoke anything with a parameter that is not a string, I get an error:
Procedure or function 'generalLogonFromSessionID' expects parameter '#sessionID', which was not supplied.
The code I am using to test this is:
var dataRow = _storedProcedureProvider.GetDataRow("generalLogonFromSessionID", new List<SqlParameter>
{
new SqlParameter("#sessionID", SqlDbType.UniqueIdentifier, oTokenData.TokenId.Length, oTokenData.TokenId),
new SqlParameter("#ipAddress", ipAddress)
});
But if I use a stored procedure that only accepts string parameters, it works fine.
For example:
var dataRow = _storedProcedureProvider.GetDataRow("generalLogon", new List<SqlParameter>
{
new SqlParameter("#loginName", oTokenData.Update.User.EmailAddress),
new SqlParameter("#password", bookingType == BookingType.Commercial ? lastName : "password"),
new SqlParameter("#ipAddress", ipAddress)
});
Does anyone know why?
Current example is using the wrong constructor overload.
public SqlParameter(string parameterName, SqlDbType dbType, int size, string sourceColumn);
Update to use the following
new SqlParameter("#sessionID", SqlDbType.UniqueIdentifier) {
Value = oTokenData.TokenId
};
You supplied the #session parameter, but I don't see you set any value for it..
new SqlParameter("#sessionID", SqlDbType.UniqueIdentifier) { Value = oTokenData.TokenId }
Incidentally, totally appreciate that you're trying to make you life easier by creating a class here that does lots of DB donkey work and means your code becomes simpler.. but it still looks easy to make a mistake (like this). Perhaps take a look at Dapper; it would make the code to exec your proc look more like:
using (var connection = someSqlConnection)
{
var result = connection.Execute("generalLogonFromSessionID",
new {
sessionID = oTokenData.TokenId,
ipAddress = ipAddress
},
commandType: CommandType.StoredProcedure
);
//do something with result
}
No affiliation, just think it's an awesome library that is like what you're trying to achieve, but goes further.. No point reinventing the wheel?

selecting a certain column value from looping in ienumerable

I have a result of IEnumerable from a stored procedure and i am looping through the results inorder to get the value of a column(GUID). I am unsure of how to go about on getting the Guid column from my results set in the foreach loop
this is what i have:
var results = GetGuids(instId);
foreach (var item in results)
{
}
public IEnumerable GetGuids(int id)
{
using (SqlCommand _command = new SqlCommand("StoredProc"))
{
_command.Connection = new SqlConnection(conString);
_command.Connection.Open();
_command.CommandType = CommandType.StoredProcedure;
_command.Parameters.AddWithValue("#ItemID", id);
return _command.ExecuteReader();
}
}
You can't use most of the normal linq extension methods directly on the non-generic IEnumerable... but you can call .Cast<T>() to make it an IEnumerable<T>. At that point, things get easier:
public IEnumerable<Guid> GetGuids(int id)
{
using (SqlCommand _command = new SqlCommand("StoredProc"))
{
_command.Connection = new SqlConnection(conString);
_command.Connection.Open();
_command.CommandType = CommandType.StoredProcedure;
_command.Parameters.Add("#ItemID", SqlDbType.Int).Value = id;
return _command.ExecuteReader()
.Cast<DbDataRecord>()
.Select(r => (Guid)r["GuidColumn"]);
}
}
You need to produce the results yourself from the SqlDataReader
var results = GetGuids(instId);
foreach (var item in results)
{
}
public IEnumerable<Guid> GetGuids(int id)
{
using (SqlCommand _command = new SqlCommand("StoredProc"))
{
_command.Connection = new SqlConnection(conString);
_command.Connection.Open();
_command.CommandType = CommandType.StoredProcedure;
_command.Parameters.AddWithValue("#ItemID", id);
var guids = new List<Guid>();
using (SqlDataReader reader = _command.ExecuteReader())
{
while (reader.Read()
{
guids.Add( (Guid)reader["GuidColumn"]);
}
}
}
}

how to retrieve values by LINQ in asp.net?

I want to set value to a literal control using LINQ. I got the result from database in var by the following code:
var result=md.StoredProc_Name(id);
Now I want to assign particular columns value to a literal. As we can do simply in asp.net as bellow with the help of datatable,
dt=obj.Test(id);
ltrlName.Text=dt.Rows[0]["Name"].ToString();
ltrlAddress.Text=dt.Rows[0]["Address"].ToString();
How can we do the same thing in LINQ?
var first = result.FirstOrDefault();
if (first != null)
{
ltrlName.Text = first.Name;
ltrlAddress.Text = first.Address;
}
Addendum - How to do this without linq to objects:
With the code below in a class called DB
var result = DB.SelectIntoItem("StoredProc_Name",
connectionString,
System.Data.CommandType.StoredProcedure,
new { param1 = "val1" });
if (!reader.Empty)
{
ltrlName.Text=result.Name;
ltrlAddress.Text=result.Address;
}
etc.
Code
public static dynamic SelectIntoItem(string SQLselect, string connectionString, CommandType cType = CommandType.Text, object parms = null)
{
using (SqlConnection conn = new SqlConnection(connectionString))
{
using (SqlCommand cmd = conn.CreateCommand())
{
dynamic result = new System.Dynamic.ExpandoObject();
cmd.CommandType = cType;
cmd.CommandText = SQLselect;
if (parms != null)
Addparms(cmd, parms);
conn.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{
if (reader.Read()) // read the first one to get the columns collection
{
var cols = reader.GetSchemaTable()
.Rows
.OfType<DataRow>()
.Select(r => r["ColumnName"]);
foreach (string col in cols)
{
((IDictionary<System.String, System.Object>)result)[col] = reader[col];
}
result.Empty = false;
if (reader.Read())
{
// error, what to do?
result.Error = true;
result.ErrorMessage = "More than one row in result set.";
}
else
{
result.Error = false;
}
}
else
{
result.Empty = true;
result.Error = false;
}
}
conn.Close();
return result;
}
}
}
private static void Addparms(SqlCommand cmd, object parms)
{
// parameter objects take the form new { propname : "value", ... }
foreach (PropertyInfo prop in parms.GetType().GetProperties())
{
cmd.Parameters.AddWithValue("#" + prop.Name, prop.GetValue(parms, null));
}
}
If you are insterested follow my GitHub, I'll be making the rest of it public soon (GitHub)

Categories