Wondering if I am going about this the right way. I am creating a C# application that loads a few variables from my App.Config.xml file. I am loading these into a "config" class (Config.cs) along with other variables that I am loading from a MySQL database. This is what my class looks like so far:
class Config
{
public static string ServerHostname = ConfigurationManager.AppSettings["ServerHostname"];
public static string SoftwareVersion = "v0.1a";
public static int StationID = DBConnector.GetStationID();
public static string StationDescription = DBConnector.GetStationDescription();
public static string StationName = ConfigurationManager.AppSettings["StationName"];
}
I am using Config.StationName to pull the Config.StationID and Config.StationDescription from a MySQL database like this in DBConnector.cs:
public static int GetStationID()
{
try
{
MySqlConnection conn = new MySqlConnection(connStr);
conn.Open();
string sql = "select station_id from station_master where station_name = #station_name limit 1";
MySqlCommand cmd = new MySqlCommand(sql, conn);
cmd.Parameters.AddWithValue("#station_name", Config.StationName);
object result = cmd.ExecuteScalar();
conn.Close();
return Convert.ToInt32(result);
}
catch (Exception ex)
{
ErrorConnection += ex.ToString();
return 0;
}
}
public static string GetStationDescription()
{
try
{
MySqlConnection conn = new MySqlConnection(connStr);
conn.Open();
string sql = "select station_description from station_master where station_id = '" + Config.StationID +"' limit 1";
MySqlCommand cmd = new MySqlCommand(sql, conn);
// cmd.Parameters.AddWithValue("#station_name", Config.StationName.ToString());
object result = cmd.ExecuteScalar();
conn.Close();
MessageBox.Show(sql);
return (result.ToString());
}
catch (Exception ex)
{
ErrorGenericDBException += ex.ToString();
MessageBox.Show(ErrorGenericDBException);
return "Error";
}
}
The DBConnector.GetStationID class works fine. It returns the int value of my station. But when I try to display the Config.StationDescription, it throws an exception of System.NullReferenceException: Object reference not set to an instance of an object at Namespace.DBConnector.GetStationDescription().
I thought that since I was using a static class for Config.StationName, i don't have to create an instance to refer to it. I need help to understand why its throwing an exception.
To me it looks like you might be mixing up what part of the code has the problem. Since you're saying that it's GetSTationDescription that throws, I'm not sure why you then assume StationName has a problem?
I'd take a look at the line object result = cmd.ExecuteScalar();, if you put a breakpoint on the line after that, does result have a value?
Another issue is that you're currently only closing the connection if an exception is not thrown, it would be safer to have the conn.Close(); inside a finally statement (or use a using statement so you don't have to worry about closing it).
Related
I'm working with a local database in a Windows Form Application. It works like charm, but I wanted to check if a record that a user searchs for is in the dataBase. I wrote the following code, but I get an error and I can't figure out how to solve it. I know that I reference a non-static object to a static method. But didn't know how to solve it. Thank in advance !
private void button1_Click(object sender, EventArgs e)
{
if (textBox1.Text.Length != 0)
{
var connString = #"Data Source=C:\Users\Andrei\Documents\Visual Studio 2010\Projects\Stellwag\Stellwag\Angajati.sdf";
using (var conn = new SqlCeConnection(connString))
{
try
{
var numePrenume = textBox1.Text.Trim().Split(' ');
var nume = numePrenume[0];
var prenume = numePrenume[1];
conn.Open();
var query = "SELECT COUNT(*) FROM info WHERE Nume='" + nume + "' AND Prenume='" + prenume + "'";
var command = new SqlCeCommand(query, conn);
var dataAdapter = new SqlCeDataAdapter(command);
var dataTable = new DataTable();
dataAdapter.Fill(dataTable);
int userCount = (int) SqlCeCommand.ExecuteScalar();
if (userCount > 0)
{
Info form = new Info(nume, prenume);
form.Show();
}
else
{
MessageBox.Show("Nu exista un angajat cu acest nume");
}
}
catch (Exception ex)
{
MessageBox.Show(ex.ToString());
}
}
}
Replace int userCount = (int) SqlCeCommand.ExecuteScalar(); with
int userCount = (int) command.ExecuteScalar();
because SqlCeCommand is a class and ExecuteScalar() is a non-static method of that class. so you cannot access ExecuteScalar() without a reference. in this statement var command = new SqlCeCommand(query, conn); you are creating a reference to that class so you can call ExecuteScalar() through this reference.
You need to use your SqlCeCommand object, not the class itself. Just change your
int userCount = (int)SqlCeCommand.ExecuteScalar();
to
int userCount = (int)command.ExecuteScalar();
A few things more;
You have an extra * after your COUNT(*). Remove it.
You should always use parameterized queries. This kind of string concatenations are open for SQL Injection attacks.
Use using statement to dispose your command and adapter automatically as you did for your connection.
First of all: I got my code running without using oop. I declared all my variables inside the same class and opened/closed the connection right before and after passing the query to the db. That worked! Now with some new experiences I tried to split my code into different classes. Now it wont work anymore.
It tells me "Connection must be valid and open". Enough text, here's my current code:
Services.cs
public static MySqlConnection conn // Returns the connection itself
{
get
{
MySqlConnection conn = new MySqlConnection(Services.ServerConnection);
return conn;
}
}
public static string ServerConnection // Returns the connectin-string
{
get
{
return String.Format("Server={0};Port=XXXX;Database=xxx;Uid=xxx;password=xxXxxXxXxxXxxXX;", key);
}
}
public static void DB_Select(string s, params List<string>[] lists)
{
try
{
MySqlCommand cmd = conn.CreateCommand();
cmd.CommandType = CommandType.Text;
string command = s;
cmd.CommandText = command;
MySqlDataReader sqlreader = cmd.ExecuteReader();
while (sqlreader.Read())
{
if (sqlreader[0].ToString().Length > 0)
{
for (int i = 0; i < lists.Count(); i++)
{
lists[i].Add(sqlreader[i].ToString());
}
}
else
{
foreach (List<string> save in lists)
{
save.Add("/");
}
}
}
sqlreader.Close();
}
catch (Exception ex)
{
MessageBox.Show("Error while selecting data from database!\nDetails: " + ex);
}
}
LoginForm.cs
private void checkUser(string username, string password)
{
using (Services.conn)
{
Services.conn.Open();
Services.DB_Select("..a short select statement..");
Services.conn.Close();
}
I guess this is all we need. I have shortened my code to get a focus on the problem.
I created Services.cs to get a global way to access the db from all forms without copy&pasting the connection info. Now when I reach my LoginForm.cs it throws an error "Connection must be valid and open". I've already debugged my code. It's all time closed. Even when passing conn.Open() it stays closed. Why?
Another try: I've also tried placing conn.Open() and conn.Close() inside Services.DB_Select(..) at the beginning and end. Same error here.
I have to say: The code worked before and I've used the same connection-string. So the string itself is surely valid.
I appreciate any help given here!
The problem is that you don't store the connection that was returned from your factory property. But don't use a property like a method. Instead use it in this way:
using (var con = Services.conn)
{
Services.conn.Open();
Services.DB_Select("..a short select statement..", con ));
//Services.conn.Close(); unnecessary with using
}
So use the same connection in the using that was returned from the property(or better created in the using) and pass it to the method which uses it. By the way, using a property as factory method is not best practise.
But in my opinion it's much better to create the connection where you use it, best place is in the using statement. And throw the con property to the garbage can, it is pointless and a source for nasty errors.
public static void DB_Select(string s, params List<string>[] lists)
{
try
{
using(var conn = new MySqlConnection(Services.ServerConnection))
{
conn.Open();
MySqlCommand cmd = conn.CreateCommand();
cmd.CommandText = s;
using( var sqlreader = cmd.ExecuteReader())
while (sqlreader.Read())
{
if (sqlreader[0].ToString().Length > 0)
{
for (int i = 0; i < lists.Count(); i++)
{
lists[i].Add(sqlreader[i].ToString());
}
}
else
{
foreach (List<string> save in lists)
{
save.Add("/");
}
}
} // unnecessary to close the connection
} // or the reader with the using-stetement
}
catch (Exception ex)
{
MessageBox.Show("Error while selecting data from database!\nDetails: " + ex);
}
}
Try to restructure your Services class as follows
public static MySqlConnection conn // Returns the connection itself
{
get
{
MySqlConnection conn = new MySqlConnection(Services.ServerConnection);
return conn;
}
}
private static string ServerConnection // Returns the connectin-string - PRIVATE [Improved security]
{
get
{
return String.Format("Server={0};Port=XXXX;Database=xxx;Uid=xxx;password=xxXxxXxXxxXxxXX;", key);
}
}
// Rather than executing result here, return the result to LoginForm - Future improvement
public static void DB_Select(MySqlConnection conn ,string s, params List<string>[] lists)
{
try
{
MySqlCommand cmd = conn.CreateCommand();
cmd.CommandType = CommandType.Text;
string command = s;
cmd.CommandText = command;
MySqlDataReader sqlreader = cmd.ExecuteReader();
while (sqlreader.Read())
{
if (sqlreader[0].ToString().Length > 0)
{
for (int i = 0; i < lists.Count(); i++)
{
lists[i].Add(sqlreader[i].ToString());
}
}
else
{
foreach (List<string> save in lists)
{
save.Add("/");
}
}
}
sqlreader.Close();
}
catch (Exception ex)
{
MessageBox.Show("Error while selecting data from database!\nDetails: " + ex);
}
}
In LoginForm.cs use returning connection and store it there. When you need to execute query, use
MySqlConnection conn=Services.conn(); // Get a new connection
Services.DB_Select(conn,"..a short select statement.."); // Executing requirement
Services.conn.Close();
Additional - I suggest you need to return MySqlDataReader to LoginForm and handle results there
private MySqlConnection _conn;
public MySqlConnection conn // Returns the connection itself
{
get
{
if(_conn == null)
_conn = new MySqlConnection(Services.ServerConnection);
return _conn;
}
}
Im a beginner to C# (.net of course) and for my final year project im developing a payroll system. Now I have some issues regarding ado.net sql connection object.
To keep the connection string centrally I have used a separate class call db. Taking another step to this centralization thinking, I've initialized the connection object also centrally in this db class as follows.
class db
{
string connectionString = ("connection string will be here...");
public SqlConnection GetConn()
{
SqlConnection NewConn = new SqlConnection(connectionString);
return NewConn;
}
}
Now Im using this connection object as follows in my application...
I just want to know whether I would face issues in future because of this practice and also appreciate if one of experts could explain me what is the best practice in this regard.
Thanks in advance
class client
{
db NewDB = new db(); // db class is instantiated...
SqlConnection newCon; // object referece newConn is created...
//Method to insert new clients to 'client' table
public void addNewClient(DateTime entDate, client NewClient)
{
try
{
newCon = NewDB.GetConn(); // connection object is assigned to newCon... but this is optional and I put this for the clarity
string CommandString = "INSERT INTO client(Client_Name, C_Add, Contact_Person, C_Mob_No, C_Tel_No, Remarks, Ent_Date)" +
" VALUES (#CName, #CAdd, #CPerson, #CMob, #CTel, #Remarks, #entDate)";
SqlCommand SqlCom = new SqlCommand();
SqlCom.CommandText = CommandString;
SqlCom.Parameters.Add("#CName", SqlDbType.VarChar).Value = NewClient.CName;
SqlCom.Parameters.Add("#CAdd", SqlDbType.VarChar).Value = NewClient.CAdd;
SqlCom.Parameters.Add("#CPerson", SqlDbType.VarChar).Value = NewClient.CPerson;
SqlCom.Parameters.Add("#CMob", SqlDbType.Char).Value = NewClient.CMob;
SqlCom.Parameters.Add("#CTel", SqlDbType.Char).Value = NewClient.CTel;
SqlCom.Parameters.Add("#Remarks", SqlDbType.VarChar).Value = NewClient.Remarks;
SqlCom.Parameters.Add("#entDate", SqlDbType.Date).Value = entDate;
SqlCom.Connection = newCon;
newCon.Open();
SqlCom.ExecuteNonQuery();
}
catch
{
throw;
}
finally
{
newCon.Close(); // newCon object is global to entire class so can call its close method.
}
}
}
You don't need to use global connection object. Your db connections are stored in connection pool. So you won't run out of connections. Read more about connections pooling.
Looking at your client class it is bad practice to write raw SQl in the code. It is better practice to write a stored procedure and call it from the code passing the parameters.
public void addNewClient(DateTime entDate, client NewClient)
{
try
{
newCon = NewDB.GetConn(); // create connection
conn.Open(); //open connection
// create a command object identifying the stored procedure
SqlCommand SqlCom = new SqlCommand("Store Procedure name", newConn);
// add parameter to sql command, which is passed to the stored procedure
SqlCom .Parameters.Add(new SqlParameter("#CName", NewClient.CName));
// Rest of parameters
// execute the command
cmd.ExecuteReader();
}
}
Creating a class to store the connection is good practice. However you could expand on this further.
public struct slqParameters
{
public object ParamData { get; set; }
public string ParamKey { get; set; }
public SqlDbType ParamDatabaseType { get; set; }
public int ParamSize { get; set; }
}
class db
{
private string connectionString = ("connection string will be here...");
public static void ExecuteStoreProcedure(string ProcedureName, ref slqParameters[] CommandParameters)
{
string str_ConnectionString = ConfigurationManager.ConnectionStrings["ConnectionString"].ConnectionString;
try
{
using (SqlConnection sqlConnection = new SqlConnection(str_ConnectionString))
{
using (SqlCommand sqlCommand = new SqlCommand(sProcedureName, sqlConnection) { CommandType = CommandType.StoredProcedure })
{
// Add all the parameters to the sql command.
foreach (slqParametersParameter in CommandParameters)
{
// Add a parameter
sqlCommand.Parameters.Add(new SqlParameter(Parameter.ParamKey, Parameter._ParamDatabaseType , Parameter._ParamSize ) { Value = Parameter.ParamData });
}
sqlConnection.Open();
DataTable dtTable = new DataTable();
sqlCommand.ExecuteReader())
}
}
}
catch (Exception error)
{
throw error;
}
}
}
This is only a ruff guide as i have not tested it yet but it should work
To use it on your page
Public SomeMethod()
{
slqParameters[] parameters = new Parameters[1]
{
new sqlParameters{ ParamData = , paramKey = "#CName", ParamDatabaseType = NewClient.CName}
};
db.ExecuteStoreProcedure("Store Procedure name", parameters);
}
I have a SQL template method that I want to return a string as well as a variety of methods that execute queries that I want to get the string from:
private string sqlQueryReturnString(Action<SqlConnection> sqlMethod)
{
string result = "";
SqlConnection conn = new SqlConnection();
conn.ConnectionString = ConfigurationManager.ConnectionStrings["ApplicationServices"].ConnectionString;
try
{
//open SQL connection
conn.Open();
result = sqlMethod(conn);
}
catch (Exception ex)
{
System.Diagnostics.Debug.Write(ex.ToString());
}
finally
{
conn.Close();
}
return result;
}
//Get the primary key of the currently logged in user
private string getPKofUserLoggedIn(SqlConnection conn)
{
int result = 0;
SqlCommand getPKofUserLoggedIn = new SqlCommand("SELECT [PK_User] FROM [User] WHERE [LoginName] = #userIdParam", conn);
//create and assign parameters
getPKofUserLoggedIn.Parameters.AddWithValue("#userIdParam", User.Identity.Name);
//execute command and retrieve primary key from the above insert and assign to variable
result = (int)getPKofUserLoggedIn.ExecuteScalar();
return result.ToString();
}
Above is how I would think this is approached. THis would be the call:
string test = sqlQueryReturnString(getPKofUserLoggedIn);
THis part is not working:
result = sqlMethod(conn);
It appears sqlMethod is presumed void.
What do I do to get the functionality I want?
You want a Func<SqlConnection, string>. Action is for void methods, Func for methods that return something.
I would like to return a variable out of this method.
YES I did make it static string. And tried to return a variable where the Messagebox.Show is. I even had that equal a variable and tried to return it. But I can't seem to return from inside the while brackets. And I can't get the variable outside the brackets to return. WHAT TO DO? The code works using a MessageBox, but I need the string variable.
static void rsnREAD(string dbTbl)
{
OleDbConnection machStopDB = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + #"C:\Users\sgarner\Google Drive\Visual Studio 2012\Write_to_db\Write_to_db\Machine_Stop.accdb");
//string sDate;
//sDate = DateTime.Now.ToString("MM/dd/yyy HH:mm:ss");
string str = "SELECT LAST(REASON) AS lastREASON FROM "+dbTbl+"";
OleDbCommand rdCmd = new OleDbCommand(str, machStopDB);
try
{
machStopDB.Open();
OleDbDataReader reader = rdCmd.ExecuteReader();
while (reader.Read())
{
MessageBox.Show(reader[0].ToString());
}
reader.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
finally
{
machStopDB.Close();
}
machStopDB.Close();
}
Just create the String variable outside the try block and set it when you read the value from database. Also change the return type of the method to string instead of void. You can use if instead of while because you are reading only one value from database.
static string rsnREAD(string dbTbl)
{
string result = string.Empty;
using(var machStopDB = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=" + #"C:\Users\sgarner\Google Drive\Visual Studio 2012\Write_to_db\Write_to_db\Machine_Stop.accdb");
{
string str = "SELECT LAST(REASON) AS lastREASON FROM "+dbTbl+"";
OleDbCommand rdCmd = new OleDbCommand(str, machStopDB);
try
{
machStopDB.Open();
using(var reader = rdCmd.ExecuteReader())
{
if(reader.Read())
{
result = reader[0].ToString();
}
}
}
catch (Exception ex) // Sample only. Catch only ones you need.
{
MessageBox.Show(ex.Message);
}
}
return result;
}
Therefore, if the returned value is empty string either database is empty or there was an error.