This is my current pattern
private void ReadData(string connString, string cmdString)
{
using (OracleConnection conn = new OracleConnection(connString))
{
conn.Open();
OracleCommand cmd = new OracleCommand(cmdString, conn);
OracleDataReader reader = cmd.ExecuteReader();
//some long operation using reader
}
}
In the above case, the connection remains open while the long operation is going on. Is there a way I could close the connection but still preserve the reader. Is that going to be advantageous?
If by long operations you mean to say that you need to do extra operations on the database (like update/insert/delete), then you cannot close the connection.
If you want to read the data and do some calculation based on it, then you should modify you pattern to: 1. read all the data, 2. close the connection, 3. do long operation on the data.
using System;
using System.Collections.Generic;
using Oracle.DataAccess.Client;
namespace Utils
{
class Test
{
private class Class
{
public string FirstProperty { get; set; }
public string SecondProperty { get; set; }
}
private void ReadData(string connString, string cmdString)
{
List<Class> data = new List<Class>();
using (OracleConnection conn = new OracleConnection() { ConnectionString = connString })
using (OracleCommand objCmd = new OracleCommand()
{
Connection = conn,
CommandText = cmdString
})
{
try
{
conn.Open();
}
catch (OracleException)
{
OracleConnection.ClearPool(conn);
conn.Open();
}
using (OracleDataReader dataReader = objCmd.ExecuteReader())
{
while (dataReader.Read())
data.Add(new Class()
{
FirstProperty = dataReader.GetString(0),
SecondProperty = dataReader.GetString(1)
});
}
conn.Close();
}
//some long operation using data
}
}
}
Related
We have third party class which only accepts arrays of object.
Third Party class :
public class Test
{
public class Input
{
public int testVar { get; set; }
public int testVar2 { get; set; }
}
//some methods
public static List<someType> Convert(Input[] data)
{
//execute some steps
}
}
in DB, we have the data column which we are interested and it has thousand of records.
Id data
1 new Test.Input{ testVar=12,testVar=19}
2 new Test.Input{ testVar=16,testVar=12}
3 new Test.Input{ testVar=26,testVar=11}
-
i am trying to create a class and invoke Convert method of Test class by providing array of Input type object .
public class ImplementTest
{
public void CallConvert()
{
// get the data from DB in list
List<object> Input = new List<object>();
using (SqlConnection con = new SqlConnection(conString))
{
using (SqlCommand cmd = new SqlCommand())
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandText = "dbo.ReadAll_Input";
con.Open();
using (SqlDataReader dr = cmd.ExecuteReader())
{
while (dr.Read())
{
Input.Add(dr["data"]);
}
}
}
}
//convert list object to input type object
var inputs = new Test.Input[]
{
//how to pass Input list efficiently
};
var output = Test.Convert(inputs).ToArray();
}
}
can anyone help me on passing Input list object to Create array of object efficiently please?
Thanks!
You can use a Mapper method:
public Input MapRow(IDataRecord row)
{
return new Input
{
Name = row["Name"].ToString(),
Number = int.Parse(row["Number"].ToString())
};
}
And use it like this:
public void CallConvert()
{
// get the data from DB in list
List<Input> inputs = new List<Input>();
using (SqlConnection con = new SqlConnection(conString))
{
using (SqlCommand cmd = new SqlCommand())
{
cmd.CommandType = System.Data.CommandType.StoredProcedure;
cmd.CommandText = "dbo.ReadAll_Input";
con.Open();
using (SqlDataReader dr = cmd.ExecuteReader())
{
while (dr.Read())
{
inputs.Add(MapRow(dr));
}
}
}
}
var output=Test.Convert(inputs.ToArray());
}
And to make it work, your stored procedure should return a table of inputs with, in this case, two columns (Name, Number)
I'm trying to create a Data Framework in the form of a .dll so that I can reference it when creating new projects, as opposed to reinventing the wheel with each project I create.
I have an app.config in which I store my SQL connections, a class that uses the app.config to build my SQL ConnectionString (ConnectionStrings.cs) and a Logic class (Logic.cs) that'll build whatever objects I require from the SQL Server.
Here's the classes in the .dll:
ConnectionStrings.cs:
using System.Configuration;
using System.Data.SqlClient;
namespace DataFramework
{
public static class ConnectionStrings
{
static string _liveConnectionString = ConfigurationManager.ConnectionStrings["LiveDataSource"].ConnectionString;
static string _liveMISConnectionString = ConfigurationManager.ConnectionStrings["LiveDataSource_MIS"].ConnectionString;
static string _devConnectionString = ConfigurationManager.ConnectionStrings["DevDataSource"].ConnectionString;
static string _devMISConnectionString = ConfigurationManager.ConnectionStrings["DevDataSource_MIS"].ConnectionString;
public static SqlConnection CreateLiveConnection
{
get { return new SqlConnection(_liveConnectionString); }
}
public static SqlConnection CreateLiveMISConnection
{
get { return new SqlConnection(_liveMISConnectionString); }
}
public static SqlConnection CreateDevConnection
{
get { return new SqlConnection(_devConnectionString); }
}
public static SqlConnection CreateDevMISConnection
{
get { return new SqlConnection(_devMISConnectionString); }
}
}
}
Logic.cs:
using System;
using System.Threading.Tasks;
using System.Data;
using System.Data.SqlClient;
namespace DataFramework
{
public class Logic
{
SqlConnection liveConnection = ConnectionStrings.CreateLiveMISConnection;
SqlConnection devMISConnection = ConnectionStrings.CreateDevMISConnection;
public bool IsConnecting { get; set; }
public string ConnectionMessage { get; set; }
public async Task<DataTable> ResultDataTable(bool connectToLive, string commandText, CommandType commandType)
{
DataTable dt = new DataTable();
using (SqlCommand command = new SqlCommand())
{
try
{
command.CommandType = commandType;
command.CommandTimeout = 360000000;
switch (connectToLive)
{
case true:
command.CommandText = commandText;
command.Connection = liveConnection;
if (liveConnection.State == ConnectionState.Connecting)
{
IsConnecting = true;
ConnectionMessage = "Connecting to Data Source...";
}
if (liveConnection.State != ConnectionState.Closed)
liveConnection.Close();
if (liveConnection.State != ConnectionState.Open)
{
liveConnection.Open();
IsConnecting = false;
ConnectionMessage = "";
}
break;
case false:
command.CommandType = commandType;
command.CommandText = "";
command.Connection = devMISConnection;
if (devMISConnection.State == ConnectionState.Connecting)
{
IsConnecting = true;
ConnectionMessage = commandText;
}
if (devMISConnection.State != ConnectionState.Closed)
devMISConnection.Close();
if (devMISConnection.State != ConnectionState.Open)
{
devMISConnection.Open();
IsConnecting = false;
ConnectionMessage = "";
}
break;
}
using (SqlDataReader reader = await command.ExecuteReaderAsync())
{
dt.Load(reader);
}
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message, "An Error Has Occured", System.Windows.Forms.MessageBoxButtons.OK, System.Windows.Forms.MessageBoxIcon.Error);
}
finally
{
if (devMISConnection.State != ConnectionState.Closed)
devMISConnection.Close();
if (liveConnection.State != ConnectionState.Closed)
liveConnection.Close();
}
}
return dt;
}
}
}
I include this dll as a reference in the app that I'm writing:
using System.Data;
using System.Threading.Tasks;
using System.Windows.Forms;
using DataFramework;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
DataTable dt = new DataTable();
DataFramework.Logic logic = new Logic();
public Form1()
{
InitializeComponent();
}
private async void Form1_Load(object sender, EventArgs e)
{
dt = await Task.Run(() => logic.ResultDataTable(true, "SELECT * FROM MIS.dbo.ETL_Table", CommandType.StoredProcedure));
}
}
}
The code throws the exception here:
SqlConnection liveConnection = ConnectionStrings.CreateLiveMISConnection;
So why, when I'm initializing the class, do I get this issue?
When you reference a DLL (or project) from another project, the app.config from the top most project is used. So, if you have your DataFramework being called from your WinformsApp, then your WinformsApp needs to have the right config settings in it. By default, it will ignore any app.config in the DataFramework. A bit frustrating at times! Copy your settings from your DataFramework app.config in to the WinformsApp app.config and it will work.
Another unrelated observation is that you have the following:
"SELECT * FROM MIS.dbo.ETL_Table", CommandType.StoredProcedure
The command type should be text and not a stored procedure.
I have two classes SqlHelper and DishesTypes there are used in a DAL project
public class SqlHelper
{
public static SqlDataReader ExecuteReader(string procedure,
params SqlParameter[] commandParameters)
{
using (var connection = new SqlConnection(
ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
using (var command = new SqlCommand(procedure, _connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddRange(commandParameters);
return command.ExecuteReader();
}
}
public class DishesTypes
{
public static SqlDataReader DishesTypesSelectAll()
{
return SqlHelper.ExecuteReader("DishesTypesSelectAllRows"); //name of procedure
}
}
And I have class DishedTypes that used in a BLL project like this
public class DishesTypes
{
public int DishTypeId { get; set; }
public string DishType { get; set; }
public static List<DishesTypes> DishesTypesSelectAll()
{
IDataReader dr = DataAccessLayer.DishesTypes.DishesTypesSelectAll();
List<DishesTypes> dishesTypesList = new List<DishesTypes>();
while (dr.Read())
{
DishesTypes myDishesTypes = new DishesTypes
{
DishTypeId = (int)dr["DishTypeId"],
DishType = (string)dr["DishType"]
};
dishesTypesList.Add(myDishesTypes);
}
return dishesTypesList;
}
}
Problems starts here while (dr.Read()),The reason, the connection to this point has already closed and it is necessary to reconnect how best to change the implementation of classes adhering layers DAL and BLL, to work?
If you want to roll your own, something like this is better:
public class DataQuery
{
private readonly string _connectionString;
public DataQuery(string connectionString)
{
_connectionString = connectionString;
}
public IEnumerable<T> GetList<T>(string procedure,
Func<IDataRecord, T> entityCreator,
params SqlParameter[] commandParameters
)
{
var result = new List<T>();
using (var connection = CreateConnection())
using (var command = CreateCommand(procedure, connection, commandParameters))
using (var reader = command.ExecuteReader())
{
result.Add(entityCreator(reader));
}
return result;
}
private SqlConnection CreateConnection()
{
var connection = new SqlConnection(_connectionString);
connection.Open();
return connection;
}
private static DbCommand CreateCommand(string procedure,
SqlConnection connection, SqlParameter[] commandParameters)
{
var command = new SqlCommand(procedure, connection)
{
CommandType = CommandType.StoredProcedure
};
command.Parameters.AddRange(commandParameters);
return command;
}
}
Which you would call like this:
var connectionString = ConfigurationManager.ConnectionStrings["DefaultConnection"]
.ConnectionString;
var query = new DataQuery(connectionString);
Func<IDataRecord, DishesTypes> creator = dr =>
new DishesTypes
{
DishTypeId = (int)dr["DishTypeId"],
DishType = (string)dr["DishType"]
};
var results = query.GetList("DishesTypesSelectAllRows", creator);
Otherwise, which I recommend, have a look at Dapper.
Dapper would allow you to simply do:
var results = connection.Query<DishesTypes>("DishesTypesSelectAllRows",
commandType: CommandType.StoredProcedure);
First of all, your using statement is closing your connection, so you cannot expect to return a useable IDataReader. Second, your connection is never opened, so you would not get a result, anyway. Having said that, if your dataset will always be small enough to fit in memory, you could use something like what I have done below. This should have minimal impact on your code.
public class SqlHelper
{
public static IDataReader ExecuteReader(string procedure, params SqlParameter[] commandParameters)
{
using (var connection = new SqlConnection(ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString))
{
connection.Open();
using (var command = new SqlCommand(procedure, connection))
{
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddRange(commandParameters);
DataTable dt = new DataTable();
using (SqlDataAdapter da = new SqlDataAdapter(command))
da.Fill(dt);
return dt.CreateDataReader();
}
}
}
}
public class DishesTypes
{
public static IDataReader DishesTypesSelectAll()
{
return SqlHelper.ExecuteReader("DishesTypesSelectAllRows");//name of procedure
}
}
Is the following defensive programming?
What I mean is that if it loses the connection, or some problem occurs during run-time and then the user runsit again will the .NET framework have tidied up any open connections and objects that were created when it first ran?
I've heard mention of a "Singleton pattern" - is this something I should use in the static method CreateConnection?
class Program {
static void Main(string[] args) {
DataTable CasTable = fillSampleDataTable("SELECT top 100 * FROM x");
//do other stuff
}
static SqlConnection CreateConnection() {
SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["XXX"].ConnectionString);
return conn;
}
static SqlDataAdapter CreateAdapter(string myCommand) {
SqlDataAdapter myAdapt = new SqlDataAdapter(myCommand, CreateConnection());
return myAdapt;
}
static DataTable fillSampleDataTable(string myCommand) {
using (var adapt = CreateAdapter(myCommand)) {
DataSet mySet = new DataSet();
adapt.Fill(mySet, "SampleData");
return mySet.Tables["SampleData"];
}
}
}
I would recommend you using the ADO.NET connection pool, a.k.a disposing the connections as soon as you have finished using them => wrap all IDisposable resources in using statements:
class Program
{
static void Main(string[] args)
{
DataTable CasTable = fillSampleDataTable("SELECT top 100 * FROM x");
//do other stuff
}
static DataTable fillSampleDataTable(string myCommand)
{
var connectionString = ConfigurationManager.ConnectionStrings["XXX"].ConnectionString;
using (var conn = new SqlConnection(connectionString))
using (var cmd = conn.CreateCommand())
using (var adapt = new SqlDataAdapter(cmd, conn))
{
conn.Open();
cmd.CommandText = myCommand;
DataSet mySet = new DataSet();
adapt.Fill(mySet, "SampleData");
return mySet.Tables["SampleData"];
}
}
}
But normally DataSets and DataTables are artifacts of the past. Today you are better off using strongly typed models.
So define a model:
public class MyModel
{
public int Id { get; set; }
public string Name { get; set; }
}
and then write a method that will return a list of those models:
class Program
{
static void Main(string[] args)
{
var models = SelectTop100Models("SELECT top 100 * FROM x");
//do other stuff
}
static IEnumerable<MyModel> SelectTop100Models()
{
var connectionString = ConfigurationManager.ConnectionStrings["XXX"].ConnectionString;
using (var conn = new SqlConnection(connectionString))
using (var cmd = conn.CreateCommand())
{
conn.Open();
cmd.CommandText = "SELECT top 100 * FROM x";
using (var reader = cmd.ExecuteReader())
{
while (reader.Read())
{
yield return new MyModel
{
Id = reader.GetInt32(reader.GetOrdinal("ID")),
Name = reader.GetString(reader.GetOrdinal("Name")),
};
}
}
}
}
}
Alternatively you might consider using an ORM framework such as the ADO.NET Entity Framework as it will simplify you querying the relational database and working directly with your strongly typed models using LINQ queries.
Not a big deal but for neatness sake is there any way to "create and open" a SqlConnection?
I naively wrote this code:
using (var strConnection = new SqlConnection(sourceConnection))
using (var strCommand = new SqlCommand(query, strConnection))
using (var reader = strCommand.ExecuteReader())
{
...
}
Which of course fails on line 3 because the connection isn't open.
Is there a neat way to avoid that nesting that opening the connection introduces?
using (var strConnection = new SqlConnection(sourceConnection))
{
strConnection.Open();
using (var strCommand = new SqlCommand(query, strConnection))
using (var reader = strCommand.ExecuteReader())
{
...
}
}
Good question, my idea is an Extension-Method for SqlConnection.
Check this:
public static class SqlExtensions {
public static SqlConnection OpenAndReturn(this SqlConnection con) {
try {
con.Open();
return con;
} catch {
if(con != null)
con.Dispose();
throw;
}
}
}
Usage:
using(var strConnection = new SqlConnection("CONNECTION").OpenAndReturn())
using(var strCommand = new SqlCommand("QUERY", strConnection))
using(var reader = strCommand.ExecuteReader()) {
//...
}
What about something like that:
class SqlHelper : IDisposable
{
public SqlHelper(string connectionString, string query) { ... }
public SqlConnection Connection { get; set; }
public SqlCommand Command { get; set; }
// SQL querying logic here
public void Execute() { ... }
/** IDisposable implementation **/
}
and in your code
using (SqlHelper sql = new SqlHelper(sourceConnection, query))
{
var reader = sql.Execute();
...
}