Currently I'm working on cleaning up some code on the backend of an application I'm contracted for maintenance to. I ran across a method where a call is being made to the DB via Oracle Data Reader. After examining the SQL, I realized it was not necessary to make the call to open up Oracle Data Reader seeing how the object being loaded up was already within the Context of our Entity Framework. I changed the code to follow use of the Entity Model instead. Below are the changes I made.
Original code
var POCs = new List<TBLPOC>();
Context.Database.Connection.Open();
var cmd = (OracleCommand)Context.Database.Connection.CreateCommand();
OracleDataReader reader;
var SQL = string.Empty;
if (IsAssociate == 0)
SQL = #"SELECT tblPOC.cntPOC,INITCAP(strLastName),INITCAP(strFirstName)
FROM tblPOC,tblParcelToPOC
WHERE tblParcelToPOC.cntPOC = tblPOC.cntPOC AND
tblParcelToPOC.cntAsOf = 0 AND
tblParcelToPOC.cntParcel = " + cntParcel + " ORDER BY INITCAP(strLastName)";
else
SQL = #"SELECT cntPOC,INITCAP(strLastName),INITCAP(strFirstName)
FROM tblPOC
WHERE tblPOC.cntPOC NOT IN ( SELECT cntPOC
FROM tblParcelToPOC
WHERE cntParcel = " + cntParcel + #"
AND cntAsOf = 0 )
AND tblPOC.ysnActive = 1 ORDER BY INITCAP(strLastName)";
cmd.CommandText = SQL;
cmd.CommandType = CommandType.Text;
using (reader = cmd.ExecuteReader())
{
while (reader.Read())
{
POCs.Add(new TBLPOC { CNTPOC = (decimal)reader[0],
STRLASTNAME = reader[1].ToString(),
STRFIRSTNAME = reader[2].ToString() });
}
}
Context.Database.Connection.Close();
return POCs;
Replacement code
var sql = string.Empty;
if (IsAssociate == 0)
sql = string.Format(#"SELECT tblPOC.cntPOC,INITCAP(strLastName),INITCAP(strFirstName)
FROM tblPOC,tblParcelToPOC
WHERE tblParcelToPOC.cntPOC = tblPOC.cntPOC
AND tblParcelToPOC.cntAsOf = 0
AND tblParcelToPOC.cntParcel = {0}
ORDER BY INITCAP(strLastName)",
cntParcel);
else
sql = string.Format(#"SELECT cntPOC,INITCAP(strLastName), INITCAP(strFirstName)
FROM tblPOC
WHERE tblPOC.cntPOC NOT IN (SELECT cntPOC
FROM tblParcelToPOC
WHERE cntParcel = {0}
AND cntAsOf = 0)
AND tblPOC.ysnActive = 1
ORDER BY INITCAP(strLastName)",
cntParcel);
return Context.Database.SqlQuery<TBLPOC>(sql, "0").ToList<TBLPOC>();
The issue I'm having right now is when the replacement code is executed, I get the following error:
The data reader is incompatible with the specified 'TBLPOC'. A member of the type 'CNTPOCORGANIZATION', does not have a corresponding column in the data reader with the same name.
The field cntPOCOrganization does exist within tblPOC, as well as within the TBLPOC Entity. cntPOCOrganization is a nullable decimal (don't ask why decimal, I myself don't get why the previous contractors used decimals versus ints for identifiers...). However, in the past code and the newer code, there is no need to fill that field. I'm confused on why it is errors out on that particular field.
If anyone has any insight, I would truly appreciate it. Thanks.
EDIT: So after thinking on it a bit more and doing some research, I think I know what the issue is. In the Entity Model for TBLPOC, the cntPOCOrganization field is null, however, there is an object tied to this Entity Model called TBLPOCORGANIZATION. I'm pondering if it's trying to fill it. It too has cntPOCOrganization within itself and I'm guessing that maybe it is trying to fill itself and is what is causing the issue.
That maybe possibly why the previous contractor wrote the Oracle Command versus run it through the Entity Framework. I'm going to revert back for time being (on a deadline and really don't want to play too long with it). Thanks!
This error is issued when your EF entity model does not match the query result. If you post your entity model you are trying to fetch this in, the SQL can be fixed. In general you need to use:
sql = string.Format(#"SELECT tblPOC.cntPOC AS <your_EF_model_property_name_here>,INITCAP(strLastName) AS <your_EF_model_property_name_here>,INITCAP(strFirstName) AS <your_EF_model_property_name_here>
FROM tblPOC,tblParcelToPOC
WHERE tblParcelToPOC.cntPOC = tblPOC.cntPOC
AND tblParcelToPOC.cntAsOf = 0
AND tblParcelToPOC.cntParcel = {0}
ORDER BY INITCAP(strLastName)",
cntParcel);
Related
Having a little bit of a strange error here that I have never encountered before. I have an application where users can type in a list of accounts in a datagrid and a date range and press a button and it will return the data for these accounts in a datagrid and give them the option to export it to an excel file. This works perfectly for me, logged in under my username and even when I log in under other people's username. The problem is when they try it, they get no data back. No errors, just it doesn't pull any data.
The interesting thing is this is all in the same database as the other information which they access without any problem. The only difference, which I think might be the explanation is I am calling this SQL code directly from the Application whereas everything else is called using stored procedures that sit on the server. The reason for this is I have to concatenate the SQL Query string for each item in the accounts field. Since they are able to enter as many accounts as they want, I cannot use a stored procedure since I don't know how many parameters it will have ultimately(if someone could let me know a method of doing this, I would actually prefer this way for keeping things consistent). Obviously the query string is working properly, as it's pulling data back for me, but the question I have is why is it failing to return data for others? The connection string is an SQL Authentication, so it shouldn't have anything to do with them not having Windows Authentication on the server, plus they are already able to log in to the application and it displays data on their dashboard, which couldn't happen...
Anyone that can point me in the right direction with this I would appreciate it...the only thing I can think of is it is an issue with using an in-code SQL string versus a stored procedure, but this doesn't make any sense since other people do this all the time in applications without issue.
public ICommand GetData
{
get => new RelayCommand(() =>
{
//call the SQL Code to lookup the account numbers
var SQLStr = "SELECT * FROM [Clients].[Data] WHERE (Account_Number = '";
for (var i = 0; i< AccountNums.Count; i++)
{
if (!String.IsNullOrEmpty(AccountNums[i].accNum)) SQLStr += i == 0 ? $"{AccountNums[i].accNum}'" : $" OR Account_Number = '{AccountNums[i].accNum}'";
}
SQLStr += $") AND SUB_QUERY_CREATED_ON BETWEEN '{StartDate.ToString()}' AND '{EndDate.ToString()}'";
_Data = DBMethods.GetSQLData(_Data, new Models.Clients.Data(), SQLStr, new List<string> { "ID" }, true);
ShowResPnl = true; //there are results, toggle the panel visibility bound variable
});
}
public static ObservableCollection<T> GetSQLData<T>(ObservableCollection<T> myCollection, T myClass, String SQLString, List<string> remParams, bool UseSQLQuery) where T : class
{
var conn = new SqlConnection();
try
{
var paramList = GenerateSQLParameters(myClass, remParams);
using (getConnection(conn))
{
conn.Open();
using (SqlCommand cmd = new SqlCommand(SQLString, conn))
{
cmd.CommandType = CommandType.Text;
SqlDataReader reader;
reader = cmd.ExecuteReader();
//only execute if the reader has data
if (reader.HasRows)
{
while (reader.Read())
{
var tempModel = Global.GenerateNewInstance(myClass) as T;
Type model = tempModel.GetType();
var prop = model.GetProperties();
PropertyInfo pi;
//set the values for each property in the model
foreach (var p in prop)
{
if (!remParams.Contains(p.Name))
{
pi = tempModel.GetType().GetProperty(p.Name);
if (reader[p.Name] == DBNull.Value)
{
pi.SetValue(tempModel, null);
}
else
{
pi.SetValue(tempModel, reader[p.Name]);
}
}
}
myCollection.Add(tempModel);
}
reader.Close();
cmd.Dispose();
}
}
}
}
catch (Exception ex)
{
ErrorWindow errWin = new ErrorWindow("There was a problem trying to Get the Data with the Query '" + SQLString + "'! Error: " + ex.Message);
errWin.Show();
}
return myCollection;
}
UPDATE: OK I got it working perfectly with help from THIS thread:
How do I split a string so I can access item x?
and more specifically this post:
What about using string and values() statement?
DECLARE #str varchar(max)
SET #str = 'Hello John Smith'
DECLARE #separator varchar(max)
SET #separator = ' '
DECLARE #Splited TABLE(id int IDENTITY(1,1), item varchar(max))
SET #str = REPLACE(#str, #separator, '''),(''')
SET #str = 'SELECT * FROM (VALUES(''' + #str + ''')) AS V(A)'
INSERT INTO #Splited
EXEC(#str)
SELECT * FROM #Splited
I created a stored procedure using this, then did a left join on Account numbers from the Data Table and used a WHERE clause to set the Start and End Dates and exclude items that were NULL(checked one of the columns). Works perfectly and only took about 2 or 3 seconds to return the data. I had another working method as detailed here https://sqlperformance.com/2012/07/t-sql-queries/split-strings#comments using a function which was taking well over a minute to return data for only 4 accounts...obviously was not going to work well enough so I found the method mentioned prior and it works excellently!
I have a table in SQLite with the following structure:
+-------+------+------+
| id |name |value |
|int |text |int |
+-------+------+------+
I am attempting to follow this guide on using sqlite databases in Unity. I am able to query the sqlite_master table to retrieve the only database table name using the following code:
_dbConnection = (IDbConnection)new SqliteConnection(_dbURI);
_dbConnection.Open();
string sqlStatement = "SELECT name FROM sqlite_master WHERE type='table' AND name='" + tableName + "';";
_dbCommand = _dbConnection.CreateCommand();
_dbCommand.CommandText = sqlStatement;
_dbReader = _dbCommand.ExecuteReader();
while (_dbReader.Read())
{
Debug.Log("Table: " + _dbReader.GetString(0));
}
However, when I attempt to query from the table itself to get the maximum ID using similar code my DataReader returns a null row:
_dbConnection = (IDbConnection)new SqliteConnection(_dbURI);
_dbConnection.Open();
string sqlStatement = "SELECT max(id) FROM " + tableName + ";";
_dbCommand = _dbConnection.CreateCommand();
_dbCommand.CommandText = sqlStatement;
_dbReader = _dbCommand.ExecuteReader();
while (_dbReader.Read())
{
Debug.Log("MaxId = " + _dbReader.GetInt32(0));
}
The null causes a conversion error with the call to _dbReader.GetInt32(0). When I write the sql statement to the log and paste it into my database browser it yields a number, so the query should be returning a value.
I'm not clear on why similar code works when querying sqlite_master but not my other table.
I've also tried ExecuteScalar with no success.
Thanks!
EDIT:
tableName is equal to "unit_def"
error message is
InvalidCastException: Cannot cast from source type to destination type.
Mono.Data.Sqlite.SqliteDataReader.VerifyType (Int32 i, DbType typ)
using count(*) yields a 0, so it seems that unity can't see the data in the table?
Also, oddly, changing the code messed up the GUI elements in my editor for some reason.
I solved the issue, hopefully this answer is helpful to others:
The issue was tricky to troubleshoot since
the correct database was being referenced
all code was correct
the sql query did return a result when run in my database browser
The problem is that I am using the DB Browser for Sqlite, which I am new to. I had written rows of data to the database so that my query would return results within the browser, but apparently these changes are not written to the database file for other applications to see until the "Write Changes" button is pushed.
Hopefully this helps anyone else who might be new to the db browser and stumbles into this error!
I'm trying to execute a prepared sql query which updates CLOB fields in an Oracle 10g database (10.2.0.1).
If I execute the following query from inside SQL Developer and supply the values for the placeholders, there is no prblem. If I however execute it through an OracleCommand (Oracle.DataAccess.dll, version 1.102.0.1 (I think), .NET Framework 3.5),
I get the error message below. Note that we are not using the default oracle client as we require bulk insertion. The given ODP version and .NET Framework version are unfortunately a hard requirement and we may not change that.
Query:
UPDATE master_table
SET description = :description,
modification_notes = :modification_notes
WHERE master_id = :master_id;
Error:
ORA-00932: inconsistent datatypes: expected - got CLOB
Further Inormation:
Parameters are assigned as follows:
var param_description = new OracleParameter(":description", OracleDbType.Clob);
param_description.Value = "Test";
I have tried the following things:
insert to_clob() into the SQL query
assign a Oracle.DataAccess.Types.OracleClob object to the parameter.
I have also found the following description, but I would really want to be able to keep the prepared query.
How to insert CLOB field in Oracle using C#
Is it possible to do this through a prepared query?
I've attached a complete example which produces the error. DESCRIPTION and MODIFICATION_NOTES are two columns of type CLOB in the database.
Input data:
connection: OracleConnection to the database
master_id: primary key to filter for
Code:
Disclaimer: I typed the following example by hand, there might be mistakes which are not in the actual code
var query = "UPDATE master_table " +
"SET description = :description " +
" modification_notes = :modification_notes " +
"WHERE master_id = :master_id";
var param_master_id = new OracleParameter(":master_id", OracleDbType.Int64);
param_master_id.Value = master_id;
var param_description = new OracleParameter(":description", OracleDbType.Clob);
param_description.Value = "Test1";
var param_master_id = new OracleParameter(":modification_notes", OracleDbType.Clob);
param_description.Value = "Test2";
IDbCommand command = new OracleCommand(query, connection);
command.parameters.Add(param_master_id);
command.parameters.Add(param_description);
command.parameters.Add(param_modification_notes);
command.ExecuteNonQuery(); // this line throws an exception
You need to set this to true if you want to bind by name. Default is bind by the order of the parameter added.
cmd.BindByName = true;
Edit: My answer below applies for typical use of Clobs where the size is greater than 32k (what they were designed for). If you know you will always be binding less than 32k bytes, or 16k characters in the usual case of unicode you can bind as Varchar2 and free yourself from having to create a temporary lob.
--
Keep in mind that a LOB in an oracle column is really a LOB Locator, a pointer to the actual data. Before you can update a CLOB column with that Lob Locator, you need to create and populate a temporary CLOB first.
In the ODP.NET samples directory in your Oracle Home there should be a LOB directory, in there it looks like samples5.cs might be a good place to start. Here is a snippet from it:
// Set the command
OracleCommand cmd = new OracleCommand(
"update multimedia_tab set story = :1 where thekey = 1");
cmd.Connection = con;
cmd.CommandType = CommandType.Text;
// Create an OracleClob object, specifying no caching and not a NCLOB
OracleClob clob = new OracleClob(con, false, false);
// Write data to the OracleClob object, clob, which is a temporary LOB
string str = "this is a new story";
clob.Write(str.ToCharArray(), 0, str.Length);
// Bind a parameter with OracleDbType.Clob
cmd.Parameters.Add("clobdata",
OracleDbType.Clob,
clob,
ParameterDirection.Input);
try
{
// Execute command
cmd.ExecuteNonQuery();
See the accepted answer for the actual solution.
[Edit: Former suspected answer]:
After several days of testing and debugging I found the solution which was so far away from everything I considered:
Apparently, you need to bind all Clob fields first before binding anything else - even when using actual placeholders instead of using :1, :2 etc.
Changing the bind order (i.e. the order of the AddParameter calls) fixed it.
Try This :
string Query3 = " DECLARE " +
"str varchar2(32767); " +
" BEGIN " +
" str := '" + base64ImageRepresentationLogo + "'; " +
" update map_general_settings set value=str where DESC_AR='LOGO_IMG' ; END; ";
command.CommandText = Query3;
command.ExecuteNonQuery();
Maybe this is not the best source I've ever written but it is for a simple form that has the goal to write data remotely.
I've two MySQLConnections both for a local database. localconnection is used to read the DB and updateconnection edit every single row. The problem is that when i'm trying to update the Database the program raise a timeout and it crash.
I think the problem is generated by the while loop.
My intention is to read a single row, post it on the server and update it if the server returns the status equals 200.
Here's the code, it fails on updateConnection.ExcecuteNonQuery();
// Local Database here.
localCommand.Parameters.Clear();
// 0 - Grab unsent emails
string receivedMessages = "SELECT * FROM EMAIL WHERE HASSENT = 0";
// Update connection init START
string updateConnectionString = "Server=" + this.localServer + ";Database=" + this.localDatabase + ";Uid=" + this.localUser + ";Pwd=" + this.localpassword;
MySqlConnection updateConnection = new MySqlConnection(updateConnectionString);
updateConnection.Open();
MySqlTransaction transaction = updateConnection.BeginTransaction();
MySqlCommand updateCommand = new MySqlCommand();
// Update connection init END
localCommand.Connection = localConnection;
localCommand.Prepare();
try
{
localCommand.CommandText = receivedMessages;
MySqlDataReader reader = localCommand.ExecuteReader();
while (reader.Read()) // Local db read
{
String EID = reader.GetString(0);
String message = reader.GetString(3);
String fromEmail = reader.GetString(6);
String toEmail= reader.GetString(12);
// 1 - Post Request via HttpWebRequest
var receivedResponse = JObject.Parse(toSend.setIsReceived(fromEmail, message, toEmail));
// 2 - Read the JSON response from the server
if ((int)receivedResponse["status"] == 200)
{
string updateInbox = "UPDATE EMAIL SET HASSENT = 1 WHERE EMAILID = #EID";
MySqlParameter EMAILID = new MySqlParameter("#EID", MySqlDbType.String);
EMAILID.Value = EID; // We use the same fetched above
updateCommand.Connection = updateConnection;
updateCommand.Parameters.Add(IID_value);
updateCommand.Prepare();
updateCommand.CommandText = updateInbox;
updateCommand.ExecuteNonQuery();
}
else
{
// Notice the error....
}
}
}
catch (MySqlException ex)
{
transaction.Rollback();
// Notice...
}
finally
{
updateConnection.Close();
}
It is hard to tell exactly what's wrong here without doing some experiments.
There are two possibilities, though.
First, your program appears to be running on a web server, which necessarily constrains it to run for a limited amount of time. But, you loop through a possibly large result set, and do stuff of an uncontrollable duration for each item in that result set.
Second, you read a result set row by row from the MySQL server, and with a different connection try to update the tables behind that result set. This may cause a deadlock, in which the MySQL server blocks one of your update queries until the select query completes, thus preventing the completion of the select query.
How to cure this? First of all, try to handle a fixed and small number of rows in each invocation of this code. Change your select query to
SELECT * FROM EMAIL WHERE HASSENT = 0 LIMIT 10
and you'll handle ten records each time through.
Second, read in the whole result set from the select query, into a data structure, then loop over the items. In other words, don't nest the updates in the select.
Third, reduce the amount of data you handle by changing SELECT * to SELECT field, field, field.
I'm using Oracle's ODAC.NET for a .NET 3.5 project against an Oracle 11 Express database, and I'm seeing behavior that I can't explain (and can't seem to work around).
ODAC should be the latest, I just pulled it 3 days ago, but the versions are as follows:
Oracle.DataAccess.dll version 2.112.3.0 (release 5)
oci.dll (instant client) version 11.2.0.1
I have a Table, People, that has 3 columns:
ID
FirstName
LastName
In code I run an ALTER TABLE command, using OracleCommand.ExecuteNonQuery, to add a new column named "MIDDLE_NAME" to the table. That command succeeds. If I look at the table with Oracle SQL Developer, the columns shows up. All well and good.
Now if I run use OracleCommand.ExecuteReader with a command text of SELECT * FROM People right after I do the alter table, I get back data with only 3 columns, not 4!
Here is code that reproduces the problem:
public void FieldTest()
{
var sql1 = "CREATE TABLE People (" +
"ID NUMBER PRIMARY KEY, " +
"FirstName NVARCHAR2 (200), " +
"LastName NVARCHAR2 (200) NOT NULL)";
var sql2 = "ALTER TABLE People " +
"ADD Middle_Name NUMBER";
var sql3 = "SELECT * FROM People";
var sql4 = "SELECT column_name FROM all_tab_cols WHERE table_name = 'PEOPLE'";
var cnInfo = new OracleConnectionInfo("192.168.10.246", 1521, "XE", "system", "password");
var connectionString = BuildConnectionString(cnInfo);
using (var connection = new OracleConnection(connectionString))
{
connection.Open();
using (var create = new OracleCommand(sql1, connection))
{
create.ExecuteNonQuery();
}
using (var get = new OracleCommand(sql3, connection))
{
using (var reader = get.ExecuteReader())
{
Debug.WriteLine("Columns: " + reader.FieldCount);
// outputs 3, which is right
}
}
using (var alter = new OracleCommand(sql2, connection))
{
alter.ExecuteNonQuery();
}
using (var get = new OracleCommand(sql3, connection))
{
using (var reader = get.ExecuteReader())
{
Debug.WriteLine("Columns: " + reader.FieldCount);
// outputs 3, which is *wrong* <---- Here's the problem
}
}
using (var cols = new OracleCommand(sql4, connection))
{
using (var reader = cols.ExecuteReader())
{
int count = 0;
while (reader.Read())
{
count++;
Debug.WriteLine("Col: " + reader.GetString(0));
}
Debug.WriteLine("Columns: " + count.ToString());
// outputs 4, which is right
}
}
}
}
I've tried some things to prevent the behavior, and none of them give me back the 4th column:
I close the connection and re-open it
I use a new OracleConnection for the SELECT than for the ALTER
I use the same OracleConnection for the SELECT and for the ALTER
I use a new OracleCommand for the SELECT than for the ALTER
I use the same OracleCommand for the SELECT and for the ALTER
I call PurgeStatementCache on the connection between the ALTER and SELECT
I call FlushCache on the connection between the ALTER and SELECT
I explicitly Close and Dispose the OracleCommand and OracleConnection (as opposed to the using block) used for the ALTER and SELECT
Restarted the calling PC and the PC hosting the Oracle database.
If I look at the column list by doing a SELECT * FROM all_tab_cols, the new column is there.
The only thing that seems to work reliably is closing the app and re-starting it (well it's from a unit test, but it's a shutdown and restart of the test host). Then I get that 4th column. Sometimes I can use breakpoints and re-execute queries and the 4th column will appear, but nothing that is specifically repeatable with straight execution of code (meaning without setting a break point and moving the execution point back up).
Something in the bowels of ODAC seems to be caching the schema of that table, but I can figure out what, why or how to prevent it. Anyone have any experience with this, or ideas how I might prevent it?
I know this answer comes years later but if new readers run into problems with caching try setting:
Metadata Pooling = false, Self Tuning = False and Statement Cache Size = 0
...in the connection string. Keep in mind that there are performance implications for doing so.
https://docs.oracle.com/database/122/ODPNT/featConnecting.htm#GUID-0CFEB161-68EF-4BC2-8943-3BDFFB878602
Maybe post some of your C# code. The following is a test that behaves as expected, meaning I can see the new column immediately after adding it. This is using odp 11.2 rel 5 hitting an 11g db, using 4.0 framework:
The test table is:
CREATE TABLE T1
(
DTE DATE default sysdate
);
Drop and recreate it after each run of the following C# code (a bit dirty but anyway):
string connStr = "User Id=xxx;Password=yyy;Data Source=my11gDb;";
using (OracleConnection con = new OracleConnection(connStr))
{
string s = "ALTER TABLE T1 ADD (added_col VARCHAR2(10))";
using (OracleCommand cmd = new OracleCommand(s, con))
{
con.Open();
cmd.ExecuteNonQuery();
string s2 = "select column_name from all_tab_columns where table_name = 'T1'";
//con.FlushCache(); // doesn't seem to matter, works with or without
using (OracleCommand cmd2 = new OracleCommand(s2, con))
{
OracleDataReader rdr = cmd2.ExecuteReader();
for (int i = 0; rdr.Read(); i++)
{
Console.WriteLine("Column {0} => {1}",i+1,rdr.GetString(0));
}
rdr.Close();
}
}
}
Output:
Column 1 => DTE
Column 2 => ADDED_COL
Edit:
Ah, ok, I see what you're saying, it looks like statement caching. I played around with changing the cache size to 0 (in conn string, use "Statement Cache Size=0"), and also tried cmd.AddToStatementCache = false, but these did not work.
One thing that does work is to use a slightly different string, like adding a space. I know its a hack, but this is all I can get to work for me anyway.
Try your example with:
var sql3 = "SELECT * FROM People";
var sql5 = "SELECT * FROM People "; // note extra space
And use sql3 before adding column, and sql5 after adding a column.
Hope that helps