I’m following an online course and in the course they explain how you can retrieve data from a database. Creating the connection and commands are done by a DbProviderFactories class. I understand the code in the course but is using using for the connection, command and reader necessary? Also, are the null checks necessary? The code looks cluttered and if you have a lot of models in your database (Continent, Country, Currency, …) it would require a lot of copy/paste which is bad?
So the question really is, is the code below rather good or bad and what could be improved upon? The goal is to use SQLite as database provider. Does this work with the approach below?
public static ObservableCollection<Continent> GetContinents()
{
var continents = new ObservableCollection<Continent>();
var provider = ConfigurationManager.ConnectionStrings["DbConnection"].ProviderName;
var connectionString = ConfigurationManager.ConnectionStrings["DbConnection"].ConnectionString;
using (var connection = DbProviderFactories.GetFactory(provider).CreateConnection())
{
if (connection == null) return null;
connection.ConnectionString = connectionString;
connection.Open();
using (var command = DbProviderFactories.GetFactory(provider).CreateCommand())
{
if (command == null) return null;
command.CommandType = CommandType.Text;
command.Connection = connection;
command.CommandText = "SELECT * FROM Continent";
using (var reader = command.ExecuteReader())
while (reader.Read())
continents.Add(new Continent(reader["Code"].ToString(), reader["EnglishName"].ToString()));
}
}
return continents;
}
using using for the connection, command and reader necessary?
Yes.
Here I commented the code
using (var command = DbProviderFactories.GetFactory(provider).CreateCommand()) // here you've created the command
{
if (command == null) return null;
command.CommandType = CommandType.Text;
command.Connection = connection;
command.CommandText = "SELECT * FROM Continent";
using (var reader = command.ExecuteReader()) //Here you're reading what the command returned.
while (reader.Read())
continents.Add(new Continent(reader["Code"].ToString(), reader["EnglishName"].ToString()));
}
Also, are the null checks necessary?
It could return null data so yes absolutely
The code looks cluttered
Such is the coder life brotha. Using loops for objects will save on space.
Related
I'm trying to get from my database some data, each of that data may have some attributes, so my logic was while i'm getting all data so while the MySqlDataReader is executed i was going to execute the query for each id of data i got to get it's attributes.
But i run in to error: 'There is already an open DataReader associated with this Connection' so my guess is that i can't run at the same time the MySqlDataReader so at this point, which would be the best approach to get attributes for each data?
Should i just cycle on each Plu element after i've added them to the list or is there a better solution?
Here is the function where i get the data (Plu object)
public IEnumerable<Plu> GetPlu(string piva, int menu)
{
string connectionString = $"CONSTR";
using var connection = new MySqlConnection(connectionString);
connection.Open();
var sql = #"QUERY";
using var cmd = new MySqlCommand(sql, connection);
cmd.Parameters.AddWithValue("#menu", menu);
cmd.Prepare();
using MySqlDataReader reader = cmd.ExecuteReader();
List<Plu> plu = new List<Plu>();
while (reader.Read())
{
plu.Add(new Plu(
(int)reader["ID_PLUREP"],
(string)reader["CODICE_PRP"],
(string)reader["ESTESA_DES"],
(string)reader["DESCR_DES"], (float)reader["PRE_PRP"],
reader.IsDBNull(reader.GetOrdinal("IMG_IMG")) ? null : (string)reader["IMG_IMG"],
Attributi(connection, (int)reader["ID_PLUREP"])
));
}
return plu;
}
And here is function Attributi which return the IEnumerable of attributes for each Plu
public IEnumerable<Plu.Attributi> Attributi(MySqlConnection connection, int idplu)
{
var sql = #"QUERY";
using var cmd = new MySqlCommand(sql, connection);
cmd.Parameters.AddWithValue("#idplu", idplu);
cmd.Prepare();
List<Plu.Attributi> attributi = new List<Plu.Attributi>();
using MySqlDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
attributi.Add(new Plu.Attributi(
reader.IsDBNull(reader.GetOrdinal("BCKCOL_ATT")) ? null : (string)reader["BCKCOL_ATT"],
reader.IsDBNull(reader.GetOrdinal("FORCOL_ATT")) ? null : (string)reader["FORCOL_ATT"],
reader.IsDBNull(reader.GetOrdinal("DESCR_ATT")) ? null : (string)reader["DESCR_ATT"]
));
}
return null;
}
You can't use an open connection with a reader already executing. Open a new connection in Attributi.
public IEnumerable<Plu.Attributi> Attributi(int idplu)
{
var sql = #"QUERY";
using var connection = new MySqlConnection(connectionString)
{
connection.Open();
using var cmd = new MySqlCommand(sql, connection)
{
cmd.Parameters.AddWithValue("#idplu", idplu);
cmd.Prepare();
List<Plu.Attributi> attributi = new List<Plu.Attributi>();
using MySqlDataReader reader = cmd.ExecuteReader()
{
while (reader.Read())
{
attributi.Add(new Plu.Attributi(
reader.IsDBNull(reader.GetOrdinal("BCKCOL_ATT")) ? null : (string)reader["BCKCOL_ATT"],
reader.IsDBNull(reader.GetOrdinal("FORCOL_ATT")) ? null : (string)reader["FORCOL_ATT"],
reader.IsDBNull(reader.GetOrdinal("DESCR_ATT")) ? null : (string)reader["DESCR_ATT"]
));
}
return null;
}
}
}
BTW, your usage of using is totally off. You need a block after the using statement where you deal with everything regarding the IDisposable object.
EDIT: Apparently that's a new .NET Core 3.1 feature.
For the more general case, my experience with MySQL has lead me to always "free" my reader with:
MySqlDataReader reader = cmd.ExecuteReader();
DataTable dataTable = new DataTable();
dataTable.Load(reader);
Then working from the DataTable rather than the MySqlDataReader, you can then reuse the connection as you prefer.
I know people are going to scream that this topic is all over the internet. I know, I've read them. And I still don't understand it. I simply want to populate my object from the results of a stored procedure. Now this stored procedure takes at least 2 parameters - an action and what to find.
#Action
#customername
The #Action simply determine what the stored procedure needs to do and returns the results. So for example if I want to update a customer, I'd call the sp through my object:
public class Customer()
{
//properties
public int UpdateCustomer()
{
using (SQLConnection connection = new SqlConnection(Helper.CnnVal("DataConnection")))
{
SQLCommand = new SqlCommand(Helper.Procedure("Customer"), connection);
command.CommandType = CommandType.StoredProcedure;
SqlParameterCollection parameterCollection = command.Parameters;
parameterCollection.Add("#Action", SqlDbType.NVarChar, -1).Value = "Update"
//complete the rest of it....
}
}
}
This works well. So the problem arises when I simply want to populate the object with the results of the sp. In this case I would pass "Retrieve" as the #Action parameter and this.customer_name as the #customername parameter to the sp.
But how do I put the results from the stored procedure into the object?
I have this...
public void GetCustomer()
{
using (SQLConnection connection = new SqlConnection(Helper.CnnVal("DataConnection")))
{
var retrieval = new DynamicParameters();
retrieval.Add("#Action", "Retrieve");
retrieval.Add("#name", this.customer_Name);
connection.Open():
connection.Execute(Helper.Procedure("Customer"), retrieval, commandType: CommandType.StoredProcedure);
}
}
But I don't think it's working.
Back a long time ago I used to run a "fetch" for PHP when I needed to populate an object. Should I go back to this?
You need to execute a SqlReader on the command, Something like this:
using (var connection = new SqlConnection("Connection"))
using (var command = new SqlCommand("Retrieve", connection))
{
command.CommandType = System.Data.CommandType.StoredProcedure;
command.Parameters.AddWithValue("#Action", "Retrieve");
command.Parameters.AddWithValue("#name", this.customer_Name);
connection.Open();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
var item = new YourClass();
// You can use GetX() methods to get the value of the fields
item.Name = reader.GetString("name");
// You can also access the fields by using bracket notation
item.Age = (int)reader["age"];
// Be careful of nullable fields though!
}
}
}
Using #Encrypt0r advice and guidance I got it working:
public void GetCustomer() {
using (SqlConnection connection = new SqlConnection(Helper.CnnVal("DataConnection"))) {
SqlCommand command = new SqlCommand(Helper.Procedure("Customer"), connection);
command.CommandType = CommandType.StoredProcedure;
command.Parameters.AddWithValue("#Action", "Retrieve");
command.Parameters.AddWithValue("#name", this.customer_name);
connection.Open();
using (var reader = command.ExecuteReader()) {
while (reader.Read()) {
this.tbl_id = (int)reader["tbl_id"];
this.customer_name = (string)reader["customer_name"];
this.customer_id = reader.GetInt32(customer_id);
this.customer_address = (string)reader["customer_address"];
this.customer_city = (string)reader["customer_city"];
this.customer_state = (string)reader["customer_state"];
this.customer_zip = reader.GetInt32(customer_zip);
}
}
I encountered error when get something from Oracle DB.
Here is my code:
public virtual IDataReader LoadDataReaderWithSqlString(string strQuery, ISessionScope session)
{
var s = GetSession(session);
using (var connection = s.Connection)
{
var command = connection.CreateCommand();
command.Connection = connection;
if (connection.State == ConnectionState.Closed || connection.State == ConnectionState.Broken)
connection.Open();
command.CommandType = CommandType.Text;
command.CommandText = s.CreateSQLQuery(strQuery).ToString();
s.Transaction.Enlist(command); // Set the command to exeute using the NHibernate's transaction
using (var dataReader = command.ExecuteReader())
{
if(dataReader.Read())
return dataReader;
}
}
return null;
}
When I debugging, I was able to see the return value in dataReader.
I using NHibernate to Run Raw SQL. I want to return DataReader. Anyone can help me please?
Are you are trying to return opened IDataReader? The problem is that you wrap your ExecuteReader in using statement. using means that your dataReader will be disposed after code inside of using is executed. So you return disposed object. The solution is: remove using:
var dataReader = command.ExecuteReader();
if(dataReader.Read())
return dataReader;
And same for connection object.
ADD
As David mentioned in comments you might want to avoid resources leak (I mean the case when connection was opened, but command was not executed), then you should handle exceptions like this:
public virtual IDataReader LoadDataReaderWithSqlString(string strQuery, ISessionScope session)
{
try
{
var s = GetSession(session);
var connection = s.Connection;
var command = connection.CreateCommand();
command.Connection = connection;
if (connection.State == ConnectionState.Closed || connection.State == ConnectionState.Broken)
connection.Open();
command.CommandType = CommandType.Text;
command.CommandText = s.CreateSQLQuery(strQuery).ToString();
s.Transaction.Enlist(command); // Set the command to exeute using the NHibernate's transaction
try
{
var dataReader = command.ExecuteReader();
if(dataReader.Read())
return dataReader;
}
catch (DbException)
{
// error executing command
connection.Close();
return null; // or throw; // it depends on your logic
}
}
catch (DbException)
{
// if connection was not opened
return null; // or throw; // it depends on your logic
}
return null;
}
that is because you having
using (var connection = s.Connection) and using (var dataReader = command.ExecuteReader())
using block will dispose the object ( here connection and dataReader)
remove the using block if you need to return the dataReader
Consider the FetchData method below. It is designed to avoid duplicating the database query code every time you want to fetch some data from the database.
List<MyData> myData = new List<MyData();
FetchData((IDataReader reader) =>
{
myData.Add(new MyData(reader.GetString(0), reader.GetInt32(1)));
}, "usp_get_my_data");
It works, but it would be nice if this object creation could somehow live inside the FetchData method.
Is there a better approach?
Perhaps FetchData can be modified to return a list of some type X directly?
protected void FetchData(Action<IDataReader> processor, String query)
{
using (var connection = CreateConnection())
{
connection.ConnectionString = ConnectionString;
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = query;
using (IDataReader reader = command.ExecuteReader())
{
while (reader.read())
{
processor(reader);
}
}
}
}
}
Using generics maybe?
protected IEnumerable<T> FetchData<T>(Func<IDataReader, T> processor, String query)
{
using (var connection = CreateConnection())
{
connection.ConnectionString = ConnectionString;
connection.Open();
using (var command = connection.CreateCommand())
{
command.CommandType = CommandType.StoredProcedure;
command.CommandText = query;
using (IDataReader reader = command.ExecuteReader())
{
while (reader.read())
{
yield return processor(reader);
}
}
}
}
}
Then you can use the enumeration however you want:
var myData = FetchData<MyData>(reader => new MyData(reader.GetString(0), reader.GetInt32(1)), "usp_get_my_data").ToList();
You might take a look at Dapper.Net which is single file ORM with three different helpers:
Execute a query and map the results to a strongly typed List
Execute a query and map it to a list of dynamic objects
Execute a Command that returns no results
Design patterns used to build this miro-ORM are so helpful. Dapper.Net is currently being used in StackOverflow.
This question already has answers here:
Return DataReader from DataLayer in Using statement
(4 answers)
Closed 10 years ago.
Okay, my code is currently:
public MySqlDataReader CreateQuery(string queryString, string connectionString )
{
using (MySqlConnection connection = new MySqlConnection(connectionString))
{
using (MySqlCommand command = new MySqlCommand(queryString, connection))
{
command.Connection.Open();
command.ExecuteNonQuery();
MySqlDataReader reader = command.ExecuteReader();
return reader;
}
}
}
In another function, I've got:
using(MySqlDataReader readers = _connection.CreateQuery(query, connectString))
Currently, in this form, when I return reader into a readers, there is no value. I've stepped through, and verified that at the point of the return, reader had the correct information in it. Yet readers has no values. I'm probably missing something completely silly. But any and all help in this will be appreciated. Thanks!
With the using command, your code is actually going to dispose of the connection and the command before control is returned to the caller. Essentially your connection object and your command object created in the using statements are disposed before the calling code is able to use it so its no longer usable.
You probably want to do something with the results of the query rather than the reader itself. Perhaps load it into a table? Something like:
public DataTable CreateQuery(string queryString, string connectionString )
{
DataTable results = new DataTable("Results");
using (MySqlConnection connection = new MySqlConnection(connectionString))
{
using (MySqlCommand command = new MySqlCommand(queryString, connection))
{
command.Connection.Open();
command.ExecuteNonQuery();
using (MySqlDataReader reader = command.ExecuteReader())
results.Load(reader);
}
}
return results;
}
Instead of returning the Datareader or copy all records into an in-memory DataTable that you return as a whole, you could return an IEnumerable<IDataRecord> which is the basic interface a DataReader record implements.
So this should work since the yield causes the Connection to keep alive:
public IEnumerable<IDataRecord> CreateQuery(string queryString, string connectionString)
{
using (MySqlConnection connection = new MySqlConnection(connectionString))
{
using (MySqlCommand command = new MySqlCommand(queryString, connection))
{
command.Connection.Open();
using (var reader = command.ExecuteReader())
{
while (reader.Read())
{
yield return (IDataRecord)reader;
}
}
}
}
}
If you want for example take only the first 5 records:
var records = CreateQuery("SELECT * FROM ais.country;", Properties.Settings.Default.MySQL).Take(5);
foreach (IDataRecord rec in records)
{
String country = rec.GetString(rec.GetOrdinal("Name"));
}