Object Cast Error - c#

In My AddProduct Method there is something wrong with my code. I get a message saying Object cannot be cast from DBNull to other Types when I test my code. Anybody have any ideas where this problem is coming from?
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Data;
using System.Data.SqlClient;
namespace ProductMaintenance
{
class ProductDB
{
public static Product GetProduct(string code)
{
SqlConnection connection = MMABooksDB.GetConnection();
string select = "SELECT ProductCode, Description, UnitPrice "
+ "FROM Products "
+ "WHERE ProductCode = #ProductCode";
SqlCommand selectCommand = new SqlCommand(select, connection);
selectCommand.Parameters.AddWithValue("#ProductCode", code);
try
{
connection.Open();
SqlDataReader prodReader = selectCommand.ExecuteReader(CommandBehavior.SingleRow);
if (prodReader.Read())
{
Product product = new Product();
product.Code = prodReader["ProductCode"].ToString(); ;
product.Description = prodReader["Description"].ToString();
product.Price = ((decimal)prodReader["Price"]);
return product;
}
else
{
return null;
}
}
catch (SqlException ex)
{
throw ex;
}
finally
{
connection.Close();
}
}
public static bool UpdateProduct(Product oldProduct, Product newProduct)
{
SqlConnection connection = MMABooksDB.GetConnection();
string updateStatement = "UPDATE Products SET " + "Description = #NewDescription, " + "UnitPrice = #NewUnitPrice, " + "WHERE ProductCode = #oldProductCode " + "AND Description = #OldDescription " + "AND UnitPrice = #OldUnitPrice";
SqlCommand updateCommand =
new SqlCommand(updateStatement, connection);
updateCommand.Parameters.AddWithValue(
"#NewDescription", newProduct.Description);
updateCommand.Parameters.AddWithValue(
"#NewUnitPrice", newProduct.Price);
updateCommand.Parameters.AddWithValue(
"#OldProductCode", oldProduct.Code);
updateCommand.Parameters.AddWithValue(
"#OldDescription", oldProduct.Description);
updateCommand.Parameters.AddWithValue(
"#OldUnitPrice", oldProduct.Price);
try
{
connection.Open();
int count = updateCommand.ExecuteNonQuery();
if(count > 0)
return true;
else
return false;
}
catch(SqlException ex)
{
throw ex;
}
finally
{
connection.Close();
}
}
public static int AddProduct(Product product)
{
SqlConnection connection = MMABooksDB.GetConnection();
string insertStatement = "INSERT Products " + "(ProductCode, Description, UnitPrice) " + "VALUES (#ProductCode, #Description, #UnitPrice)";
SqlCommand insertCommand = new SqlCommand(insertStatement, connection);
insertCommand.Parameters.AddWithValue("#ProductCode", product.Code);
insertCommand.Parameters.AddWithValue("#Description", product.Description);
insertCommand.Parameters.AddWithValue("#UnitPrice", product.Price);
try
{
connection.Open();
insertCommand.ExecuteNonQuery();
string selectStatement = "SELECT IDENT_CURRENT('Products') FROM Products";
SqlCommand selectCommand = new SqlCommand(selectStatement, connection);
int productC = Convert.ToInt32(selectCommand.ExecuteScalar());
return productC;
}
catch (SqlException ex)
{
throw ex;
}
finally
{
connection.Close();
}
}
}
}

You're selecting IDENT_CURRENT which can possibly return null.
See this documentation.
Returns NULL on error or if a caller does not have permission to view
the object.
In SQL Server, a user can only view the metadata of
securables that the user owns or on which the user has been granted
permission. This means that metadata-emitting, built-in functions such
as IDENT_CURRENT may return NULL if the user does not have any
permission on the object.
Based on the error you're describing, this appears to be where it's failing. Your AddProduct method wouldn't be trying to cast a null value when it does the INSERT so it is likely a null value when you try to retrieve the identity at
int productC = Convert.ToInt32(selectCommand.ExecuteScalar());
Since the call to IDENT_CURRENT is the only statement in that SQL command, that all but rules out a syntax or other error in the SQL, which would narrow this down to permissions.
There's a bit of conjecture and deduction there but I suspect that's where it's at.
(If the error is thrown from AddProduct method there's no need to post everything else. Many people - including myself - are going to jump to the conclusion that the error is when you access the SqlDataReader since you aren't checking for nulls there. But as you said, that's not the method throwing the exception. It's just easy to miss when there's so much irrelevant code.)

Related

If there is no row in database sum command return an error

An error is thrown when there is no data in data base while converting a string value into int.
try {
SqlCommand cmdc = new SqlCommand("SELECT SUM(Credited_amount) FROM IMS_Credit_Dir WHERE Credit_comp_id=1 AND Crdt_typ_id=1", con);
string companya_credit_amount = null, comapnyb_credit_amount = null;
con.Open();
SqlDataReader drc = cmdc.ExecuteReader();
if (drc.HasRows)
{
while (drc.Read())
{
companya_credit_amount = drc[0].ToString();
}
drc.Close();
con.Close();
}
SqlCommand cmdcp = new SqlCommand("SELECT SUM(Credited_amount) FROM IMS_Credit_Dir WHERE Credit_comp_id=2 AND Crdt_typ_id=1", con);
con.Open();
SqlDataReader drcp = cmdcp.ExecuteReader();
if (drcp.HasRows)
{
while (drcp.Read())
{
companyb_credit_amount = drcp[0].ToString();
}
drcp.Close();
con.Close();
}
if (!Page.IsPostBack)
{
int companyA = 0,companyB=0;
if (companya_credit_amount != "") { companyA = Convert.ToInt32(credit_amount.ToString()); }
if (companyb_credit_amount != ""){ companyB = Convert.ToInt32(companyb_credit_amount); }
int total = (companyA+companyB);
count_total_lbl.Text = "Rs." + " " + total.ToString();
count_comapnya_lbl.Text = "Rs." + " " + companya_credit_amount.ToString();
count_companyb_lbl.Text ="Rs."+" "+ companyb_credit_amount.ToString();
}
}
catch(Exception ex) { Label2.Text = ex.ToString(); }
If there is value its working fine.but when there is no value in data base there is an error msg.
System.FormatException: Input string was not in a correct format.
Use IsDBNull to check for null values
Create and destroy all your type instances that implement IDisposable in using blocks. This ensures that connections are always released and resources are cleaned up.
Do not use connections across a class. Create them when needed and then dispose of them. Sql Server will handle connection pooling.
Get the native types directly, not the string equivalent! See changes to GetInt32 instead of ToString on the data reader.
You should refactor this to use SqlParameter's and make the retrieval statement generic OR get both SUM values in 1 sql call.
There is an if (!Page.IsPostBack) statement, if none of this code does anything if it is a postback then check at the top of the page and do not execute the sql statements if it is a postback. Otherwise the code is making (possibly) expensive sql calls for no reason.
try
{
int companyA = 0,companyB=0;
using(var con = new SqlConnection("connectionStringHere"))
{
con.Open();
using(SqlCommand cmdc = new SqlCommand("SELECT SUM(Credited_amount) FROM IMS_Credit_Dir WHERE Credit_comp_id=1 AND Crdt_typ_id=1", con))
using(SqlDataReader drc = cmdc.ExecuteReader())
{
if (drc.Read() && !drc.IsDBNull(0))
companyA = drc.GetInt32(0);
}
using(SqlCommand cmdcp = new SqlCommand("SELECT SUM(Credited_amount) FROM IMS_Credit_Dir WHERE Credit_comp_id=2 AND Crdt_typ_id=1", con))
using(SqlDataReader drcp = cmdcp.ExecuteReader())
{
if (drcp.Read() && !drcp.IsDBNull(0))
companyB = drcp.GetIn32(0);
}
}
// if you are not going to do anything with these values if its not a post back move the check to the top of the method
// and then do not execute anything if it is a postback
// ie: // if (Page.IsPostBack) return;
if (!Page.IsPostBack)
{
int total = (companyA+companyB);
count_total_lbl.Text = "Rs." + " " + total.ToString();
count_comapnya_lbl.Text = "Rs." + " " + companyA.ToString();
count_companyb_lbl.Text ="Rs."+" "+ companyB.ToString();
}
}
catch(Exception ex) { Label2.Text = ex.ToString(); }
Try to replace this
SELECT SUM(Credited_amount)
WITH
SELECT ISNULL(SUM(Credited_amount),0)
Also find one confusing code while converting Credited amount values
if (companya_credit_amount != "") { companyA = Convert.ToInt32(credit_amount.ToString()); }
---------^^^^^
if (companyb_credit_amount != ""){ companyB = Convert.ToInt32(companyb_credit_amount); }
I don't know about your business requirement but What i think Instead of using credit_amount value companya_credit_amount should be use to show value for companyA variable right?
You should do 2 things:
string companya_credit_amount = "", comapnyb_credit_amount = "";
Before assigning the value to these string variable you should check for db null as following:
while (drc.Read())
{
companya_credit_amount = (drc[0] != DbNull.Value) ? drc[0].ToString() : "" ;
}
Similarely
while (drcp.Read())
{
companyb_credit_amount = (drcp[0] != DbNull.Value) ? drcp[0].ToString() : "";
}
Try it.
You need to initialize credit_amount to empty and check if db value is null as shown below:
try {
companya_credit_amount = string.Empty;
companyb_credit_amount = string.Empty;
SqlCommand cmdc = new SqlCommand("SELECT SUM(Credited_amount) FROM IMS_Credit_Dir WHERE Credit_comp_id=1 AND Crdt_typ_id=1", con);
string companya_credit_amount = null, comapnyb_credit_amount = null;
con.Open();
SqlDataReader drc = cmd
c.ExecuteReader();
if (drc.HasRows)
{
while (drc.Read())
{
companya_credit_amount = drcp.IsDBNull(0)?string.Empty:Convert.ToString(drcp[0]);
}
drc.Close();
con.Close();
}
SqlCommand cmdcp = new SqlCommand("SELECT SUM(Credited_amount) FROM IMS_Credit_Dir WHERE Credit_comp_id=2 AND Crdt_typ_id=1", con);
con.Open();
SqlDataReader drcp = cmdcp.ExecuteReader();
if (drcp.HasRows)
{
while (drcp.Read())
{
companyb_credit_amount = drcp.IsDBNull(0)?string.Empty:Convert.ToString(drcp[0]);
}
drcp.Close();
con.Close();
}
if (!Page.IsPostBack)
{
int companyA = 0,companyB=0;
if (companya_credit_amount != "") { companyA = Convert.ToInt32(credit_amount.ToString()); }
if (companyb_credit_amount != ""){ companyB = Convert.ToInt32(companyb_credit_amount); }
int total = (companyA+companyB);
count_total_lbl.Text = "Rs." + " " + total.ToString();
count_comapnya_lbl.Text = "Rs." + " " + companya_credit_amount.ToString();
count_companyb_lbl.Text ="Rs."+" "+ companyb_credit_amount.ToString();
}
}
catch(Exception ex) { Label2.Text = ex.ToString(); }

Do I need to use try-catch for every command?

I have oledb connection. I use try-catch because commands sometimes getting error. Like this:
OleDbConnection Connection;
Connection = new OleDbConnection("Provider=Microsoft.Jet.OleDb.4.0;Data Source=" +
Server.MapPath("~/db.mdb"));
OleDbCommand Command1, Command2, Command3;
Command1 = new OleDbCommand("SELECT a FROM Table1 WHERE ID = 1", Connection);
Command2 = new OleDbCommand("SELECT a FROM Table1 WHERE ID = 2", Connection);
Command3 = new OleDbCommand("SELECT a FROM Table1 WHERE ID = 3", Connection);
try
{
Connection.Open();
var1= (int)Command1.ExecuteScalar();
var2= (int)Command2.ExecuteScalar();
var3= (int)Command3.ExecuteScalar();
Connection.Close()
}
catch (Exception)
{
var1 = 2;
var2 = 24;
var3 = 55;
}
But when I get error from Command1, its go to catch and use catch values. I need to use catch values when getting error that command. Because for example Command2 and Command3 working fine. Just Command1 getting error. I can use try-catch for every command like this:
try
{
var1= (int)Command1.ExecuteScalar();
}
catch (Exception)
{
var1 = 2;
}
...
But I have 300-400 commands, I can't use try-catch for every commands(I can but so hard). How can I use catch for just getting error commands?
I hope for you that this code is just a demo and not your actual code.
This is a great example for how not to use databases.
A fast and simple solution to this will be to encapsulate the execution of the command with the try catch block. something like this:
bool TryGetInt(OleDbCommand Command, int ValueIfException, out int Value)
{
try
{
if(Connection.State == ConnectionState.Closed || Connection.State == ConnectionState.Broken)
{
Connection.Open();
}
Value = (int)Command1.ExecuteScalar();
return true;
}
catch (Exception)
{
// Consider writing the exception into a log file
Value = ValueIfException;
return false;
}
}
and call it like this:
TryGetInt(Command1, 2, out var1);
TryGetInt(Command2, 24, out var2);
....
Connection.Close();
Note that my suggested function returns a boolean value to indicate success.
Of course, you can create a dictionary to hold the commands with their fail values and iterate it using foreach.
However
I must point out that you should reconsider your design. 300 database calls are way{300} too many database calls to make in a single website, let alone a single aspx page.
You can just write a function that takes your command string and the catch value as a parameter and do the try/catch handling logic there.
You can create list of commands, add each command in list and then call in loop. Like this:
OleDbConnection Connection;
Connection = new OleDbConnection("Provider=Microsoft.Jet.OleDb.4.0;Data Source=" +
Server.MapPath("~/db.mdb"));
OleDbCommand Command1, Command2, Command3;
List<OleDbCommand> commands = new List<OleDbCommand>();
commands.Add(new OleDbCommand("SELECT a FROM Table1 WHERE ID = 1", Connection));
commands.Add(new OleDbCommand("SELECT a FROM Table1 WHERE ID = 2", Connection));
commands.Add(new OleDbCommand("SELECT a FROM Table1 WHERE ID = 3", Connection));
Connection.Open();
foreach (var command in commands)
{
try
{
var1= (int)Command.ExecuteScalar();
}
catch (Exception)
{
// ...
}
}
Connection.Close();
You can try something like the below. 2 Options given. One templated in case you are not only dealing with int. But you would suppress all Exceptions. This is generally not a good idea. Better to catch specific exceptions:
OleDbCommand b = myCommand;
int c = ExecuteScalarSuppressException(b, 24);
int d = ExecuteScalarSuppressException<int>(b, 33);
private static int ExecuteScalarSuppressException(OleDbCommand oleDbCommand, int defaultValue)
{
int returnValue = defaultValue;
try
{
defaultValue = (int)oleDbCommand.ExecuteScalar();
}
catch (Exception)
{
}
return defaultValue;
}
private static T ExecuteScalarSuppressException<T>(OleDbCommand oleDbCommand, T defaultValue)
{
T returnValue = defaultValue;
try
{
defaultValue = (T)oleDbCommand.ExecuteScalar();
}
catch (Exception)
{
}
return defaultValue;
}
If you need to execute all 300/400 commands i would rather have something like this:
for(int i=0;i<300;i++)
{
Command1 = new OleDbCommand("SELECT a FROM Table1 WHERE ID = "+i, Connection);
try{
Connection.Open();
var1= (int)Command1.ExecuteScalar();
}catch(Exception e)
{
//your logic here
}
}

Why is my SQL Server CE code failing?

In my WindowsCE / Compact Framework (.NET1.1) project, I need to create a new table in code. I thought I could do it this way:
if (! TableExists("table42"))
{
CreateTable42();
}
public static bool TableExists(string tableName)
{
try
{
using (SqlCeConnection sqlConn = new SqlCeConnection(#"Data Source=\my documents\Platypus.SDF"))
{
sqlConn.Open();
string qryStr = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = ?";
SqlCeCommand cmd = new SqlCeCommand(qryStr, sqlConn);
cmd.Parameters[0].Value = tableName;
cmd.CommandType = CommandType.Text;
int retCount = (int)cmd.ExecuteScalar();
return retCount > 0;
}
}
catch (Exception ex)
{
MessageBox.Show("TableExists ex.Message == " + ex.Message);
MessageBox.Show("TableExists ex.ToString() == " + ex.ToString());
MessageBox.Show("TableExists ex.GetBaseException() == " + ex.GetBaseException());
return false;
}
}
...but the call to TableExists() fails; and shows me:
TableExists ex.Message ==
TableExists ex.ToString() == System.Data.SqlServerCe.SqlCeException at System.Data.SqlServerCe.SqlConnection.ProcessResults(Int32 hr) at ...at Open(boolean silent) ...
TableExists ex.GetBaseException() == [same as ex.ToString() above]
"Int32 hr" ... ??? What the Hec Ramsey is that?
As documented previously in these environs, I can't step through this projct, so I rely on those calls to MessageBox.Show().
The rest of the related code, if it may be of interest, is:
public static void CreateTable42()
{
try
{
using (SqlCeConnection con = new SqlCeConnection(#"Data Source=\my documents\Platypus.SDF"))
{
con.Open();
using (SqlCeCommand com = new SqlCeCommand(
"create table table42 (setting_id INT IDENTITY NOT NULL PRIMARY KEY, setting_name varchar(40) not null, setting_value(63) varchar not null)", con))
{
com.ExecuteNonQuery();
WriteSettingsVal("table42settingname","table42settingval");
}
}
}
catch (Exception ex)
{
MessageBox.Show("CreateTable42 " + ex.Message);
}
}
public static void WriteSettingsVal(string settingName, string settingVal)
{
using (SqlCeConnection sqlConn = new SqlCeConnection(#"Data Source=\my documents\Platypus.SDF"))
{
sqlConn.Open();
string dmlStr = "insert into tabld42 (setting_name, setting_value) values(?, ?)";
SqlCeCommand cmd = new SqlCeCommand(dmlStr, sqlConn);
cmd.CommandType = CommandType.Text;
cmd.Parameters[0].Value = settingName;
cmd.Parameters[1].Value = settingVal;
try
{
cmd.ExecuteNonQuery();
}
catch (Exception ex)
{
MessageBox.Show("WriteSettingsVal " + ex.Message);
}
}
}
UPDATE
Answer to Brad Rem's comment:
I don't think it's necessary to encase the param in quotes, as other working code is like:
cmd.Parameters.Add("#account_id", Dept.AccountID);
-and:
cmd.Parameters[0].Value = Dept.AccountID;
(it does it one way the first time when in a loop, and the other way thereafter (don't ask me why).
Anyway, just for grins, I did change the TableExists() parameter code from this:
cmd.Parameters[0].Value = tableName;
...to this:
cmd.Parameters.Add("#TABLE_NAME", tableName);
...but I still get the exact same result.
UPDATE 2
Here (http://msdn.microsoft.com/en-us/library/aa237891(v=SQL.80).aspx) I found this: "Caution You must specify the SQL Server CE provider string when you open a SQL Server CE database."
They give this example:
cn.ConnectionString = "Provider=Microsoft.SQLSERVER.OLEDB.CE.2.0; data source=\Northwind.sdf"
I'm not doing that; my conn str is:
using (SqlCeConnection sqlConn = new SqlCeConnection(#"Data Source=\my documents\CCRDB.SDF"))
Could that be my problem?
UPDATE 3
I took this gent's advice (http://www.codeproject.com/Answers/629613/Why-is-my-SQLServer-CE-code-failing?cmt=487657#answer1) and added a catch for SqlCeExcpetions so that it is now:
public static bool TableExists(string tableName)
{
try
{
using (SqlCeConnection sqlConn = new SqlCeConnection(#"Data Source=\my documents\CCRDB.SDF"))
{
sqlConn.Open();
string qryStr = "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME = #TABLE_NAME";
SqlCeCommand cmd = new SqlCeCommand(qryStr, sqlConn);
cmd.Parameters.Add("#TABLE_NAME", tableName);
cmd.CommandType = CommandType.Text;
int retCount = (int)cmd.ExecuteScalar();
return retCount > 0;
}
}
catch (SqlCeException sqlceex)
{
MessageBox.Show("TableExists sqlceex.Message == " + sqlceex.Message);
MessageBox.Show("TableExists sqlceex.ToString() == " + sqlceex.ToString());
return false;
. . .
The SqlCeException message is: "There is a file sharing violation. A different process might be using the file [,,,,,]" then "...processresults ... open ... getinstance ..."
UPDATE 4
Trying to use ctacke's sample code, but: Is Transaction absolutely necessary? I had to change the code to the following for my scenario/milieu, and don't know what Transaction should be or how to build it:
public static bool TableExists(string tableName)
{
string sql = string.Format("SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{0}'", tableName);
try
{
using (SqlCeConnection sqlConn = new SqlCeConnection(#"Data Source=\my documents\HHSDB.SDF"))
{
SqlCeCommand command = new SqlCeCommand(sql, sqlConn);
//command.Transaction = CurrentTransaction as SqlCeTransaction;
command.Connection = sqlConn;
command.CommandText = sql;
int count = Convert.ToInt32(command.ExecuteScalar());
return (count > 0);
}
}
catch (SqlCeException sqlceex)
{
MessageBox.Show("TableExists sqlceex.Message == " + sqlceex.Message);
return false;
}
}
UPDATE 5
With this code, the err msg I get is, "An err msg is available for this exception but cannot be displayed because these messages are optional and are not currently insallted on this device. Please install ... NETCFv35.Messages.EN.cab"
UPDATE 6
All too typically, this legacy, ancient-technology project is giving me headaches. It seems that only one connection is allowed to be open at a time, and the app opens one from the outset; so, I have to use that connection. However, it is a DBConnection, not a SqlCeConnection, so I can't use this code:
using (SqlCeCommand com = new SqlCeCommand(
"create table hhs_settings (setting_id int identity (1,1) Primary key, setting_name varchar(40) not null, setting_value(63) varchar not null)", frmCentral.dbconn))
{
com.ExecuteNonQuery();
WriteSettingsVal("beltprinter", "ZebraQL220");
}
...because the already-open connection type passed as an arg to the SqlCeCommand constructor is DBCommand, not the expected/required SqlCeConneection.
The tentacles of this code are far too wide and entrenched to rip out by the roots and refactor to make it more sensible: a single tentative step in the foothills causes a raging avalanche on Everest.
For fun I'd try two things. First, replace the '?' parameter with a named parameter like '#tablename' and see if that changes things. Yes, I know '?' should work, but it's a confusing, ugly precedent and maybe since it's a system table it's wonky. Yes, it's a stretch, but worth a try just to know.
The second thing I'd do is something like this method from the SQLCE implementation of the OpenNETCF ORM:
public override bool TableExists(string tableName)
{
var connection = GetConnection(true);
try
{
using (var command = GetNewCommandObject())
{
command.Transaction = CurrentTransaction as SqlCeTransaction;
command.Connection = connection;
var sql = string.Format("SELECT COUNT(*) FROM information_schema.tables WHERE table_name = '{0}'", tableName);
command.CommandText = sql;
var count = Convert.ToInt32(command.ExecuteScalar());
return (count > 0);
}
}
finally
{
DoneWithConnection(connection, true);
}
}
Note that I didn't even bother parameterizing, largely because I doubt it will provide any perf benefit (queue the hordes whining about SQL injection). This way definitely works - we've got it deployed and in use in many live solutions.
EDIT
For completeness (though I'm not sure it adds to clarity).
protected virtual IDbConnection GetConnection(bool maintenance)
{
switch (ConnectionBehavior)
{
case ConnectionBehavior.AlwaysNew:
var connection = GetNewConnectionObject();
connection.Open();
return connection;
case ConnectionBehavior.HoldMaintenance:
if (m_connection == null)
{
m_connection = GetNewConnectionObject();
m_connection.Open();
}
if (maintenance) return m_connection;
var connection2 = GetNewConnectionObject();
connection2.Open();
return connection2;
case ConnectionBehavior.Persistent:
if (m_connection == null)
{
m_connection = GetNewConnectionObject();
m_connection.Open();
}
return m_connection;
default:
throw new NotSupportedException();
}
}
protected virtual void DoneWithConnection(IDbConnection connection, bool maintenance)
{
switch (ConnectionBehavior)
{
case ConnectionBehavior.AlwaysNew:
connection.Close();
connection.Dispose();
break;
case ConnectionBehavior.HoldMaintenance:
if (maintenance) return;
connection.Close();
connection.Dispose();
break;
case ConnectionBehavior.Persistent:
return;
default:
throw new NotSupportedException();
}
}
wow... still struggling... I did too when I first got started on a handheld device SQL-CE. My current project is running with C#.Net 3.5 but I think the principles you are running into are the same. Here is what is working for my system in it's close parallels to yours.
First, the connection string to the handheld. It is just
string myConnString = #"Data Source=\MyFolder\MyData.sdf";
no reference to the sql driver
Next, the TableExists
SqlCeCommand oCmd = new SqlCeCommand( "select * from INFORMATION_SCHEME.TABLES "
+ " where TABLE_NAME = #pTableName" );
oCmd.Parameters.Add( new SqlCeParameter( "pTableName", YourTableParameterToFunction ));
The "#pTableName" is to differentiate between the "TABLE_NAME" column and to absolutely prevent any issues about ambiguity. The Parameter does NOT get the extra "#". In SQL, the # indicates to look for a variable... The SqlCeParameter of "pTableName" must match as it is in the SQL Command (but without the leading "#").
Instead of issuing a call to ExecuteScalar, I am actually pulling the data down into a DataTable via
DataTable oTmpTbl = new DataTable();
SqlCeDataAdapter da = new SqlCeDataAdapter( oCmd );
da.Fill( oTmpTbl );
bool tblExists = oTbl.Rows.Count > 0;
This way, I either get records back or I dont... if I do, the number of records should be > 0. Since I'm not doing a "LIKE", it should only return the one in question.
When you get into your insert, updates and deletes, I have always tried to prefix my parameters with something like "#pWhateverColumn" and make sure the SqlCeParameter is by the same name but without the "#". I haven't had any issues and this project has been running for years. Yes it's a .net 3.5 app, but the fundamental basics of connecting and querying SHOULD be the same.
If it IS all within your application, I would try something like creating a single global static "Connection" object. Then, a single static method to handle it. Then, instead of doing a NEW connection during every "using" attempt, change it to something like...
public static class ConnectionHandler
{
static SqlCeConnection myGlobalConnection;
public static SqlCeConnection GetConnection()
{
if( myGlobalConnection == null )
myGlobalConnection = new SqlCeConnection();
return myGlobalConnection;
}
public static bool SqlConnect()
{
GetConnection(); // just to ensure object is created
if( myGlobalConnection.State != System.Data.ConnectionState.Open)
{
try
{
myGlobalConnection.ConnectionString = #"Data Source=\MyFolder\MyDatabase.sdf";
myGlobalConnection.Open();
}
catch( Exception ex)
{
// optionally messagebox, or preserve the connection error to the user
}
}
if( myGlobalConnection.State != System.Data.ConnectionState.Open )
MessageBox.Show( "notify user");
// return if it IS successful at opening the connection (or was already open)
return myGlobalConnection.State == System.Data.ConnectionState.Open;
}
public static void SqlDisconnect()
{
if (myGlobalConnection!= null)
{
if (myGlobalConnection.State == ConnectionState.Open)
myGlobalConnection.Close();
// In case some "other" state, always try to force CLOSE
// such as Connecting, Broken, Fetching, etc...
try
{ myGlobalConnection.Close(); }
catch
{ // notify user if issue}
}
}
}
... in your other class / function...
if( ConnectionHandler.SqlConnect() )
Using( SqlCeConnection conn = ConnectionHandler.GetConnection )
{
// do your stuff
}
... finally, when your app is finished, or any other time you need to...
ConnectionHandler.SqlDisconnect();
This keeps things centralized, and you don't have to worry about open/close, what the connection string is buried all over the place, etc... If you can't connect, you can't run a query, don't try to run the query if it can't even get that far.
I think it may be a permission issue on INFORMATION_SCHEMA system views. Try the following.
GRANT VIEW DEFINITION TO your_user;
See here for more details

C# reading int from mysql

Having problem reading a value from my table in mysql, is the index value i cant read the value back no matter what. all i get is the initialized value of 0 i dont get any error because it return 0, if i run the query in the database it get the correct value. i tried to use executeScalar() but with the same result .
MySqlConnection conn = new MySqlConnection(MyConString);
ulong ukey=0;
try
{
string sql_users2 = "SELECT `key` FROM `permuser` WHERE `user` = '" + myuser + "' AND `code` = '" + mycode + "'";
MySqlCommand cmdSel2 = new MySqlCommand(sql_users2, conn);
conn.Open();
MySqlDataReader dr2 = cmdSel2.ExecuteReader();
dr2.Read();
ukey = dr2.GetUInt64(dr2.GetOrdinal("key"));
// MessageBox.Show("Sorry " + myuser + " already have access to " + mycode + ",\nIf this is an extension, search for the user which key is " + ukey + " and edit the end date.", "Duplicate User Warning", MessageBoxButtons.OK, MessageBoxIcon.Warning);
dr2.Close();
dr2.Dispose();
}
catch (MySqlException ex) //catch 2
{
MessageBox.Show("catch ukey\nCan't connect to database\n" + ex.ToString());
}
conn.Close();
conn.Dispose();
You are returning a single value from your query, so you could use directly ExecuteScalar instead of ExecuteReader. (the link point to the description for SqlServer, but it is the same for MySql)
An important question to never forget is the usage of parameters instead of string concatenation.
What happen if your myuser or mycode variables contain a single quote? You get wrong results or syntax errors.
Of course, the main problem is the Sql Injection attack to never understimate.
using(MySqlConnection conn = new MySqlConnection(MyConString))
{
ulong ukey=0;
try
{
string sql_users2 = "SELECT `key` FROM `permuser` WHERE `user` = #usr AND `code` = #code";
MySqlCommand cmdSel2 = new MySqlCommand(sql_users2, conn);
conn.Open();
cmdSel2.Parameters.AddWithValue("#usr", myuser);
cmdSel2.Parameters.AddWithValue("#code", mycode);
object result = cmdSel2.ExecuteScalar();
if(result != null)
ukey = Convert.ToUInt64(result);
}
catch (MySqlException ex) //catch 2
{
MessageBox.Show("catch ukey\nCan't connect to database\n" + ex.ToString());
}
}
also I am a bit perplexed about your usage of UInt64. What kind of datatype is stored in the key column?
way is many simply:
ukey = (uint)dr2[0];

How to pass a table as parameter to MySqlCommand?

I am creating a method to select the id from any table by passing a search field.
private int SelectId(string tabela, string campo, string valor)
{
int id = 0;
using (command = new MySqlCommand())
{
command.Connection = conn;
command.Parameters.Add("#tabela", MySqlDbType.).Value = tabela;
command.Parameters.Add("#campo", MySqlDbType.Text).Value = campo;
command.Parameters.Add("#valor", MySqlDbType.VarChar).Value = valor;
command.CommandText = "SELECT `id` FROM #tabela WHERE #campo=#valor;";
try
{
id = (int)command.ExecuteScalar();
}
catch (MySqlException ex)
{
MessageBox.Show(ex.Number + " : " + ex.Message + command.CommandText);
}
catch (Exception)
{
throw;
}
}
return id;
}
But I get an MySqlException about syntax error. When i look at the Exception message, it shows me the query with the quoted table!
How do I pass the table as parameter without quotes?
Most databases won't let you specify table or column names via parameters. Parameters are meant to be for values. If you really, really need this to be dynamic, you should validate the input (it should be a known table name, with known column names within that table) and then include that in the SQL.
I agree with Jon. Here is a sample of your code with the table name inserted directly into the script, instead of as a parameter. Notice that you'll still want to validate the table and column name to prevent SQL injection. I have not included that here, but I have put in comment stubs for you.
private int SelectId(string tabela, string campo, string valor)
{
int id = 0;
using (command = new MySqlCommand())
{
command.Connection = conn;
command.Parameters.Add("#campo", MySqlDbType.Text).Value = campo;
command.Parameters.Add("#valor", MySqlDbType.VarChar).Value = valor;
// TODO: Validate table name for parameter 'tabela' to prevent SQL injection
// TODO: Validate column name for parameter 'campo' to prevent SQL injection
command.CommandText = "SELECT `id` FROM " + tabela + " WHERE #campo=#valor;";
try
{
id = (int)command.ExecuteScalar();
}
catch (MySqlException ex)
{
MessageBox.Show(ex.Number + " : " + ex.Message + command.CommandText);
}
catch (Exception)
{
throw;
}
}
return id;
}

Categories

Resources