What are the best practices working with Oracle.DataAccess.Client? - c#

I'm going over a lengthy data access code of a somewhat older app. Every function is calling a stored procedure to select something from Oracle DB. Every function more or less looks like the code below:
public List<SomeObject> GetMeSomethingFromDB(string myParam, int anotherParam)
{
OracleConnection conn = null;
OracleDataReader dataReader = null;
try
{
conn = new OracleConnection(Settings.ConnectionString);
conn.Open();
var cmd = new OracleCommand("STORED_PROC_NAME", conn);
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new OracleParameter("IN_MY_PARAM", OracleDbType.Varchar2)).Value = myParam;
cmd.Parameters.Add(new OracleParameter("IN_ANOTHER_PARAM", OracleDbType.Int32)).Value = anotherParam;
cmd.Parameters.Add(new OracleParameter("OUT_REF_CURSOR", OracleDbType.RefCursor)).Direction = ParameterDirection.Output;
dataReader = cmd.ExecuteReader();
List<SomeObject> result = new List<SomeObject>();
if (dataReader == null || !dataReader.HasRows) return result;
while (dataReader.Read())
{
SomeObject someObject = new SomeObject
{
SomeId = (int)dataReader["SOME_ID"],
SomeStringValue = dataReader["SOME_STRING_VALUE"].ToString()
};
result.Add(someObject);
}
return result;
}
catch (Exception e)
{
throw e;
}
finally
{
if (dataReader != null)
{
dataReader.Close();
dataReader.Dispose();
}
if (conn != null)
{
if (conn.State == ConnectionState.Open) conn.Close();
conn.Dispose();
}
}
}
My questions are:
Some functions use class level OracleConnection variable instead. What is preferred - function level or class level variable?
Is the check dataReader == null necessary? Would it ever be NULL after cmd.ExecuteReader() call?
Functions differ when it comes to connection Close/Dispose and reader Close/Dispose. What is the correct way/order in which to close/dispose? Wouldn't the reader automatically Close/Dispose if the connection is disposed?
I'm looking to hook up Oracle.ManagedDataAccess.Client to this project in the near future. Will anything in this code change to work with managed data access client?
Anything else, any best practices/suggestions are welcomed.
Thank you.

The using statement will simplify a lot your code.
public List<SomeObject> GetMeSomethingFromDB(string myParam, int anotherParam)
{
using (OracleConnection conn = new OracleConnection(Settings.ConnectionString))
using (OracleCommand cmd = new OracleCommand("STORED_PROC_NAME", conn))
{
conn.Open();
cmd.CommandType = CommandType.StoredProcedure;
cmd.Parameters.Add(new OracleParameter("IN_MY_PARAM", OracleDbType.Varchar2)).Value = myParam;
cmd.Parameters.Add(new OracleParameter("IN_ANOTHER_PARAM", OracleDbType.Int32)).Value = anotherParam;
cmd.Parameters.Add(new OracleParameter("OUT_REF_CURSOR", OracleDbType.RefCursor)).Direction = ParameterDirection.Output;
using (OracleDataReader dataReader = cmd.ExecuteReader())
{
while (dataReader.Read())
{
SomeObject someObject = new SomeObject
{
SomeId = (int)dataReader["SOME_ID"],
SomeStringValue = dataReader["SOME_STRING_VALUE"].ToString()
};
result.Add(someObject);
}
}
}
return result;
}
Use always a local connection object and include it in a using
statement to have a correct closing and disposal of the object (the
same holds true also for the OracleDataReader and OracleCommand). This will free your server from the memory and threads required to keep the connection with your code and performances are guaranteed by the connection pooling enabled by ADO.NET providers
No, the call is not necessary and neither the call to HasRows if you
plan to loop over the result. The reader returns false if there are
no rows or if you reach the end of the data set
See the point about the using statement. Proper using statements will remove this problem from your burdens.
You should't have any problem with this code if you use the ODP
provider from Oracle
There is no need to have a try catch if you only want to rethrow the
exception. Just let it bubble up to the upper level without
disrupting the stack trace with a throw e and all the code required in the finally statement is implicitly added by the compiler in the using closing curly brace.

Related

How to access the first column in SQL and why does this code give me error?

Can someone tell my why my expectedNumber reader throws an error
The name reader does not exist in its current context
As far as I can see all this is doing is reading the first row and first column, don't understand why the reader is throwing a tantrum.
It doesn't like the line:
ExpectedNumber = reader.GetInt16(0);
The query is :
SELECT TOP (1) [ExpectedNumber]
FROM [dbo].[MyDatabase]
WHERE id = '{0}'
Code:
try
{
using (SqlCommand cmd = new SqlCommand(string.Format(Query, id), Connection))
{
Connection.Open();
using (SqlDataReader reader = cmd.ExecuteReader())
{
// Check is the reader has any rows at all before starting to read.
if (reader.HasRows)
{
int ExpectedNumber = 0;
// Read advances to the next row.
while (reader.Read() == true)
{
// To avoid unexpected bugs access columns by name.
ExpectedNumber = reader.GetInt16(0);
}
Connection.Close();
return ExpectedResult;
}
Assert.Fail("No results returned from expected result query");
return 0;
}
}
}
catch (Exception e)
{
Connection.Close();
throw;
}
You should escape your query parameters, otherwise your code is vulnerable to SQL injection attacks, also, by using command parameters as in the example below you can make sure you are using the right data type (it seems you are trying to pass an int id as a string).
You are just trying to get one value so you don't need to use a reader and can use ExecuteScalar instead.
Finally, you don't need to handle closing the connection if you enclose it in a using block so you can avoid the try catch block as well.
string query = "SELECT TOP (1) [ExpectedNumber] FROM [dbo].[MyDatabase] WHERE id = #id";
using (var connection = new SqlConnection("connStr"))
{
connection.Open();
using (var cmd = new SqlCommand(query, connection))
{
cmd.Parameters.Add("#id", SqlDbType.Int).Value = id;
object result = cmd.ExecuteScalar();
if (result != null && result.GetType() != typeof(DBNull))
{
return (int)result;
}
Assert.Fail("No Results Returned from Expected Result Query");
return 0;
}
}
Note: this code assumes you are using SQL Server, for other systems the format of the parameters in the connection string might change, e.g. for Oracle it should be :id instead of #id.

How to execute another MySqlDataReader for each read ID?

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.

Cannot Use DbContext.Query inside a transaction

I am using EF6 to query a backend database. User can customize a temporary table and query the data from the temporary table. I am using
DataTable result = context.Query(queryStatement);
to get the result and it has been working fine.
Now the query is needed among a serious of other sqlcommand and a transaction is needed. So I have
public static DataTable GetData()
{
using (MyDbContext context = new MyDbContext())
using (DbContextTransaction tran = context.Database.BeginTransaction())
{
try
{
int rowAffected = context.Database.ExecuteSqlCommand(
"UPDATE [MyDb].dbo.[TableLocks] SET RefCount = RefCount + 1 WHERE TableName = 'TESTTABLE1'");
if (rowAffected != 1)
throw new Exception("Cannot find 'TestTable1'");
//The following line will raise an exception
DataTable result = context.Query("SELECT TOP 100 * FROM [MyDb].dbo.[TestTable1]");
//This line will work if I change it to
//context.Database.ExecuteSqlCommand("SELECT TOP 100 * FROM [MyDb].dbo.[TestTable1]");
//but I don't know how to get the result out of it.
context.Database.ExecuteSqlCommand(
"UPDATE [MyDb].dbo.[TableLocks] SET RefCount = RefCount - 1 WHERE TableName = 'TestTable1'");
tran.Commit();
return result;
}
catch (Exception ex)
{
tran.Rollback();
throw (ex);
}
}
}
But this throws an exception while executing context.Query
ExecuteReader requires the command to have a transaction when the connection
assigned to the command is in a pending local transaction. The Transaction
property of the command has not been initialized.
And when I read this article: https://learn.microsoft.com/en-us/ef/ef6/saving/transactions
It says:
Entity Framework does not wrap queries in a transaction.
Is it the reason cause this issue?
How can I use context.Query() inside a transaction?
What else I can use?
I tried all other method, none of them work - because the return datatype cannot be predicted before hand.
I just realized that, the Query method is defined in MyDbContext!
public DataTable Query(string sqlQuery)
{
DbProviderFactory dbFactory = DbProviderFactories.GetFactory(Database.Connection);
using (var cmd = dbFactory.CreateCommand())
{
cmd.Connection = Database.Connection;
cmd.CommandType = CommandType.Text;
cmd.CommandText = sqlQuery;
using (DbDataAdapter adapter = dbFactory.CreateDataAdapter())
{
adapter.SelectCommand = cmd;
DataTable dt = new DataTable();
adapter.Fill(dt);
return dt;
}
}
}
May be you are missing this section -
you are free to execute database operations either directly on the
SqlConnection itself, or on the DbContext. All such operations are
executed within one transaction. You take responsibility for
committing or rolling back the transaction and for calling Dispose()
on it, as well as for closing and disposing the database connection
And then this codebase -
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var sqlTxn =
conn.BeginTransaction(System.Data.IsolationLevel.Snapshot))
{
try
{
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.Transaction = sqlTxn;
sqlCommand.CommandText =
#"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
sqlCommand.ExecuteNonQuery();
using (var context =
new BloggingContext(conn, contextOwnsConnection: false))
{
context.Database.UseTransaction(sqlTxn);
var query = context.Posts.Where(p => p.Blog.Rating >= 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
}
sqlTxn.Commit();
}
catch (Exception)
{
sqlTxn.Rollback();
}
}
}
Specially this one -
context.Database.UseTransaction(sqlTxn);
Sorry guys, as mentioned above, I thought the Query method is from EF, but I examined the code and found it is actually coded by another developer, defined in class MyDbContext. Since this class is generated by EF, and I never think somebody have added a method.
It is
public DataTable Query(string sqlQuery)
{
DbProviderFactory dbFactory = DbProviderFactories.GetFactory(Database.Connection);
using (var cmd = dbFactory.CreateCommand())
{
cmd.Connection = Database.Connection;
cmd.CommandType = CommandType.Text;
cmd.CommandText = sqlQuery;
//And I added this line, then problem solved.
if (Database.CurrentTransaction != null)
cmd.Transaction = Database.CurrentTransaction.UnderlyingTransaction;
using (DbDataAdapter adapter = dbFactory.CreateDataAdapter())
{
adapter.SelectCommand = cmd;
DataTable dt = new DataTable();
adapter.Fill(dt);
return dt;
}
}
}

OracleDataReader. Error: Invalid operation. The connection is closed

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

C# Returning Data Objects Issue

I am trying to return a data object from my database so that i can access (for example) a customer ID within my ASP.NET website. Upon a customer logging in the object is returned. However, i am getting the error:
'Invalid attempt to read when no data is present.'
I have completed an sql query on the database (Executing my stored procedure) which returns the correct information, so i know it is there. I can only presume that there is something wrong with the following method:
using (SqlConnection sqlConn = new SqlConnection(ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString))
{
using (SqlCommand sqlComm = new SqlCommand("Select_Customer_By_UserName_And_Password", sqlConn))
{
sqlComm.Connection.Open();
try
{
sqlComm.CommandType = CommandType.StoredProcedure;
sqlComm.Parameters.Add("#Username", SqlDbType.NVarChar, 25).Value = pUsername;
sqlComm.Parameters.Add("#Password", SqlDbType.NVarChar, 25).Value = pPassword;
using (SqlDataReader sqlDR = sqlComm.ExecuteReader(CommandBehavior.SingleRow))
{
if (sqlDR.HasRows)
{
//Creating the new object to be returned by using the data from the database.
return new Customer
{
CustomerID = Convert.ToInt32(sqlDR["CustomerID"])
};
}
else
return null;
}
}
catch (Exception)
{
throw;
}
finally
{
sqlComm.Connection.Close();
}
}
}
You need to call sqlDR.Read(), otherwise the "record pointer" will to point to a record. HasRows only indicates there are actually rows you can read. To read each row (or just the first one), you need to call Read once or in a while loop.
For example:
if (reader.HasRows)
{
while (reader.Read())
...
}
Your code should read:
using (SqlDataReader sqlDR = sqlComm.ExecuteReader(CommandBehavior.SingleRow))
{
if (sqlDR.Read())
{
//Creating the new object to be returned by using the data from the database.
return new Customer
{
CustomerID = Convert.ToInt32(sqlDR["CustomerID"])
};
}
else
return null;
}
By the way: congrats on using using and parameterized queries!

Categories