I'm having problems with updating a row in the Users table of my Access DB. Here is the code below:
private void SaveProfileInfo()
{
try
{
ChangeForeColorOfStatusMsg(Color.Black);
ChangeTextOfStatusMsg("Saving new profile information...");
const string cmd = #"UPDATE Users SET LastName=#LastName,FirstName=#FirstName,MiddleName=#MiddleName,Add_Num=#Add_Num,Add_Street=#Add_Street,Add_Brgy=#Add_Brgy,Add_City=#Add_City,MobileNumber=#MobileNumber,Gender=#Gender WHERE ID=#ID;";
var dbConn = new OleDbConnection(cs);
var dbCmd = new OleDbCommand(cmd, dbConn);
dbCmd.Parameters.AddWithValue("#ID", UserLoggedIn.ID);
dbCmd.Parameters.AddWithValue("#LastName", txtLastName.Text);
dbCmd.Parameters.AddWithValue("#FirstName", txtFirstName.Text);
dbCmd.Parameters.AddWithValue("#MiddleName", txtMiddleName.Text);
dbCmd.Parameters.AddWithValue("#Add_Num", txtUnitNum.Text);
dbCmd.Parameters.AddWithValue("#Add_Street", txtStreet.Text);
dbCmd.Parameters.AddWithValue("#Add_Brgy", GetBrgySelectedItem());
dbCmd.Parameters.AddWithValue("#Add_City", GetCitySelectedItem());
dbCmd.Parameters.AddWithValue("#MobileNumber", txtMobileNumber.Text);
dbCmd.Parameters.AddWithValue("#Gender", GetGenderSelectedItem());
dbConn.Open();
dbCmd.ExecuteNonQuery();
dbConn.Close();
ChangeForeColorOfStatusMsg(Color.MediumSeaGreen);
ChangeTextOfStatusMsg("All changes have been saved! This window will close itself after two seconds.");
Thread.Sleep(2000);
CloseForm();
}
catch (Exception)
{
ChangeForeColorOfStatusMsg(Color.Crimson);
ChangeTextOfStatusMsg("Something went wrong while we were connecting to our database. Please try again later.");
hasFinishedEditting = false;
}
}
This method will be done on a separate thread, when the user updates his profile information.
UserLoggedIn is actually a field of a User class (a class that defines a row in my table), which stores all the info of the user who's currently logged in.
When I run this, it does not produce any exceptions or errors. But when I check my table, the values are not updated.
I copy-pasted these codes from the registration form (which works) that I made with this system, and modified it into an UPDATE cmd than an INSERT cmd.
I also made Change Username and Password Forms that use the same cmd as shown below:
public void ChangePass()
{
try
{
ChangeForeColorOfMsg(Color.Silver);
ChangeTextOfMsg("Changing password...");
const string cmd = "update Users set Pass=#Pass where ID=#ID";
var dbConn = new OleDbConnection(cs);
var dbCmd = new OleDbCommand(cmd, dbConn);
dbCmd.Parameters.AddWithValue("#Pass", txtNewPass.Text);
dbCmd.Parameters.AddWithValue("#ID", UserLoggedIn.ID);
dbConn.Open();
dbCmd.ExecuteNonQuery();
dbConn.Close();
ChangeTextOfMsg("Password successfully changed!");
}
catch (Exception)
{
ChangeForeColorOfMsg(Color.Silver);
ChangeTextOfMsg("A problem occurred. Please try again later.");
}
}
And these codes work for me. So I'm really confused right now as to why this update cmd for the profile information isn't working... Is there something I'm not seeing here?
OleDb cannot recognize parameters by their name. It follows a strictly positional order when sending them to your database for updates. In your code above the first parameter is the #ID but this parameter is used last in your query. Thus everything is messed up.
You just need to move the add of the #ID parameter as last in the collection
As a side note, you should be very careful with AddWithValue. It is an handy shortcut, but it has a dark side that could result in wrong queries.
Take a look at
Can we stop using AddWithValue already?
Related
I am developing an asp.net web application and I am trying to add a user xp system to it. I have a SQL Server database connected to it and I am trying to make a function that will give 5 experience points to the user.
I queried to the user that is logged in, accessed the user_xp column, and I am trying to add +5 to the old session variable for xp, then send that back into the database to be stored. Here is my code, I am not sure what is wrong with it.
void generateXp()
{
try
{
SqlConnection con = new SqlConnection(strcon);
if (con.State == ConnectionState.Closed)
{
con.Open();
}
SqlCommand cmd = new SqlCommand("UPDATE member_master_tbl SET user_xp = #user_xp WHERE " +
"user_name = '" + Session["username"].ToString().Trim() + "'", con);
int xp = 5;
int current_xp = Convert.ToInt32(Session["user_xp"]);
int new_xp = xp + current_xp;
string new_xp2 = Convert.ToString(new_xp);
cmd.Parameters.AddWithValue("user_xp", new_xp2);
}
catch (Exception ex)
{
}
}
Try renaming the SQL parameter to #user_xp.
cmd.Parameters.AddWithValue("#user_xp", new_xp2);
I don't have an accessible database to test. Also, you need to add the command to execute the query at the end.
cmd.ExecuteNonQuery()
That being said, it's a good practice to learn to separate DB queries to stored procedures or functions.
As others noted, you simply forgot to do a execute non query to run the command that you setup.
However, you can write things this way. You don't mention or note what the data type the experience points column is - I assumed "int".
So, your code block can be written this way:
using (SqlCommand cmd = new SqlCommand("UPDATE member_master_tbl SET user_xp = #user_xp WHERE user_name = #user",
new SqlConnection(strcon)))
{
cmd.Parameters.Add("#user_xp", SqlDbType.Int).Value = 5 + Session("user_xp");
cmd.Parameters.Add("#user", SqlDbType.NVarChar).Value = Session("username");
cmd.Connection.Open();
cmd.ExecuteNonQuery();
}
note how the command object has a connection object (so we don't need a separate one).
And while several people here "lamented" the string concentration to build the sql and warned about sql injection?
Actually, the introduction of # parameters for both values cleans up the code. So you get nice parameters - nice type checking, and you don't have to remember to add/use/have things like quotes around teh string, but not for numbers.
And I let .net cast the number expression from session() - this also likely is ok.
Also the "using block" also correctly cleans up the command object and also the connection object - so the using block is a good idea here.
On my button click event I want to insert a row into a table. When I click the button, I get no exception and I also don't get my messagebox to show either. I have the messagebox as a way to check to see if the query had been executed.
When I step through it skips the MessageBox and doesn't throw an exception.
private void BtnSend_Click(object sender, EventArgs e)
{
string theDate = dateTimePicker1.Value.ToString("MM-dd-yyyy");
var select = "INSERT INTO Trinity3(Date, Device_S_N, Student_Last_Name, Student_First_Name, Student_Number, School, Grade, Damage)" +
"VALUES (#Date, #Serial, #LastName, #FirstName, #StudentNum, #School, #Grade, #Damage)" +
"COMMIT";
SqlConnection connection = new SqlConnection("Data Source=CPS1113020004; Initial Catalog=Coweta Public Schools; Integrated Security=True");
// Create a SqlCommand instance
SqlCommand command = new SqlCommand(select, connection);
// Add the parameter
command.CommandType = CommandType.Text;
command.CommandText = select;
command.Parameters.AddWithValue("#Date", theDate);
command.Parameters.AddWithValue("#Serial",txtSerial.Text);
command.Parameters.AddWithValue("#LastName",txtLastName.Text);
command.Parameters.AddWithValue("#FirstName",txtFirstName.Text);
command.Parameters.AddWithValue("#StudentNum", txtStudentNum.Text);
command.Parameters.AddWithValue("#School",txtSchool.Text);
command.Parameters.AddWithValue("#Grade", txtGrade.Text);
command.Parameters.AddWithValue("#Damage", txtDamage.Text);
// Execute the query
try
{
connection.Open();
command.ExecuteNonQuery();
MessageBox.Show("Records inserted successfully");
}
catch
{
// Handle exception, show message to user...
}
finally
{
connection.Close();
}
this.Visible = false;
var searchForm = new SearchForm();
searchForm.ShowDialog();
}
You are throwing an exception but you are not seeing because there is nothing in your catch block.
Look up Try with Resources convention and always use it. This will automatically close your connection for you even if there is an exception.This is a must.
Add an exception to your catch block so you can see the error.
Your SQL string needs to have spaces after each section. When you are concatenating with "+" no extra space is created. So your query actually looks like this:
INSERT INTO Trinity3(Date, Device_S_N, Student_Last_Name, Student_First_Name, Student_Number, School, Grade, Damage)VALUES (#Date, #Serial, #LastName, #FirstName, #StudentNum, #School, #Grade, #Damage)COMMIT
Instead of writing your query in the application, you should create a stored procedure in the database that will contain all of the logic necessary to get the data. Then your application will simply call a one word stored proc instead of having a giant string representing your t-sql. ALSO you can actually test your stored proc and make sure it works without the application being involved.
Here's what I got: User selects from a checklistbox of database names one they'd like to archive. Switch case in place to catch the selection.
case "userSelection":
sqlAdapter = CreateMyAdapter("dbName", true, sqlconn, null);
sqlAdapter.SelectCommand.CommandText += "";
sqlAdapter.Fill(myDS.tableName);
sqlAdapter.Dispose();
The adapter:
private SqlDataAdapter CreateMyAdapter(string TableName, bool IncludeUpdates, SqlConnection sqlConn, SqlTransaction sqlTran)
{
SqlDataAdapter sqlAdapter = null;
SqlConnection sqlConnArchive = new SqlConnection();
strSQL = "SELECT " + TableName + ".* FROM " + TableName;
sqlAdapter = new SqlDataAdapter(strSQL, sqlConn);
// Right here, I create another sqlConnection that is pointed to
// another datasource.
sqlConnArchive = getThisOtherConnection();
SqlCommand sqlComm;
if (IncludeUpdates)
{
string strInsertSQL = "<insertQuery>";
sqlComm = new SqlCommand(strInsertSQL, sqlConnArchive);
sqlComm.Parameters.Add("#TableID", SqlDbType.Int, 0, "TableID");
// More params here...
sqlAdapter.InsertCommand = sqlComm;
// Update
// Delete
}
}
return sqlAdapter;
The issue:
As you can see sqlConn is the connection that is tied to the SELECT command. And sqlConnArchive is tied to the INSERT. The thought here is that I could select the data from DB_1 if you will, and insert it into DB_2 using the same SQLDataAdapter. But the issue that I'm running into is trying to insert. The select works fine, and at this line sqlAdapter.Fill(myDS.tableName); once fill executes the data is there. But the INSERT isn't working.
A few things:
I tested to see if perhaps SQLDataAdapter couldn't handle multiple datasources/connections, switched things around so it was pointing the the same DB just different tables, and I'm seeing the same results.
I've confirmed that the issue does not reside within the INSERT query.
There are no errors, just steps right over in debug.
I have tried several permutations of .Update() and none of them worked. This project that I've been assigned, throughout the entire thing it appears that .Fill(); is what is submitting the data back to the DB.
I've tested the database side and connectivity is a go. No issues with login, etc etc..
Any help is greatly appreciated.
Please note - I tried to place an even larger emphasis on the word "greatly" but was limited by my toolset. Apparently SOF doesn't support bold, blink, underline, flames, or embedded music.
I think you want ExecuteNonQuery.
var rowsAffected = sqlAdapter.InsertCommand.ExecuteNonQuery();
This executes the statement and then returns the number of rows affected. The Fill method won't run any InsertCommands.
I am currently writing a small application to keep track of monetary ins and outs, something just to improve my general C# skills. For my Login Screen currently I have the following Code
private void Login_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the 'spendingInsAndOutsDataSet.Users' table. You can move, or remove it, as needed.
this.usersTableAdapter.Fill(this.spendingInsAndOutsDataSet.Users);
}
private void button1_Click(object sender, EventArgs e)
{
string userNameText = userName.Text;
string passwordText = password.Text;
foreach (DataRow row in spendingInsAndOutsDataSet.Users)
{
if (row.ItemArray[4].Equals(userNameText) && row.ItemArray[5].Equals(passwordText))
{
MessageBox.Show("Login Successful");
MainGUI newForm = new MainGUI();
this.Visible = false;
newForm.Show();
break;
}
else
{
userName.Text = String.Empty;
password.Text = String.Empty;
MessageBox.Show("Login Failed");
break;
}
}
}
What I am looking to do when the Login is Sucessful is to write the MachineName of the current PC to a field in Users table in my SQL Database. That way when I come to start creating records I can quickly find my UsersId (which is a foreign key in my Transactions table).
I know you can get the Active Machine Name using the System.Enviroments path but Im unsure exactly how to go about writing the update. I know how to do it using a SqlCommand but I am wondering if there is a simpler way using the DataRows I have used within the ForEach loop.
Thanks in advance, any questions let me know.
James
Assuming it is an Access database (If not then make the necessary changes):
Use an Adapter to fill a table with your results. Then compare the row columns with the information provided by the user. Don't forget to use parameters to avoid injections that may potentially ruin your database or expose your user's information to a hacker.
DataTable dt = new DataTable();
String sql = "SELECT * FROM users WHERE user = #user and password=#password"
OleDbConnection connection = getAccessConnection();
OleDbDataAdapter da = new OleDbDataAdapter(sql, connection);
da.SelectCommand.Parameters.Add("#user", OleDbType.VarChar).Value = userNameText;
da.SelectCommand.Parameters.Add("#password", OleDbType.VarChar).Value = password.Text;
try
{
connection.Open();
da.Fill(dt);
connection.Close();
}
catch(OleDbException ex)
{
connection.Close();
MessageBox.Show(ex.ToString());
}
if(dt.Rows.Count == 1)
return true; //username && password matches
else if(dt.Rows.Count == 0)
return false; // does not match
You could also use AddWithValue for your parameters.
da.SelectCommand.Parameters.AddWithValue("#user", userNameText);
getAccessConnection() is a predefined OleDbConnection function that has the connection to the database setup and creates a new instance of the connection for you (that I have created for myself).
public OleDbConnection getAccessConnection()
{
this.connection = new OleDbConnection();
this.connection.ConnectionString = #"Provider=Microsoft.ACE.OLEDB.12.0;Data Source="
+ Classified.SOURCE + ";Jet OLEDB:Database Password="
+ Classified.PASS + ";";
return this.connection;
}
It is preferred to create classes for all of these functions for developers who may join in on the project. Also read up on C# test driven development as well.
Also it looks like your loop will break even if that one record fails, only allowing it to go to it's first record.
Onto creating your own data set and filling it with queried tables is also useful. Here is a brief example:
DataSet ds = new DataSet();
ds.Tables.Add(dt, "userSearchedTable");
ds.Tables["userSearchedTable"].Rows[0][1].ToString();
Then you can declare a specific data table within the set when ever you need to.
In your foreach loop, set the MachineName of the current PC on relevant row then at the end of the method call:
this.usersTableAdapter.Update(this.spendingInsAndOutsDataSet.Users);
This will update the database with the machine name
However looking at your code there are a few additional comments to make I'd like to add to improve what you have:
You are loading the entire data table and then checking it for the username and password. Really you query for the user ID in the database, load that single row and check the password. If you have many users, your current implementation will create a lot of network traffic.
Instead of:
foreach (DataRow row in spendingInsAndOutsDataSet.Users)
Consider using something like:
foreach (SpendingInsAndOutsDataSet.UsersRow row in spendingInsAndOutsDataSet.Users)
i.e. the strongly typed version of the data row object. This means you can use:
row.Username.Equals(userNameText)
instead of
row.ItemArray[4].Equals(userNameText)
Also if you are anticipating that this will be used over a network, you should look to encrypt the passwords.
I'd like to provide a command line interface to my db that allows the user to enter MULTIPLE database commands or queries (separated by line breaks in the textarea)
For each line, if its a query must return the results and if its a command, whether or not it was successful - thus allowing the user to paste a script into the text area and click 'GO' to have the batch executed.
I have been using a DataContext to interface with my database in the application but havent a CLUE where to start. Any assistance would be greatly appreciated
Think about the security issues that you are bringing into your Website.
Think again about the security. How can a clever user (more clever as you/me) hack into the database using this page. Maybe/probably using some misformed SQL, that you do not think about in this stage.
Use a direct SqlConnection and SqlCommand when the database you are using is SQL server. Use the oracle or other provider counterparts when you need to use these. A SqlCommand can return more as 1 result, this is handy in the case of multiple commands in one query. See the NextResult method for more information.
As the previous answer points out, please don't do this if it's a publicly accessible site!
If you must do it, the following code is close to what you're after - with a bit of modification you'll get exactly what you want.
public static bool ExecuteSql(string sqlScript)
{
bool success = true;
using (SqlConnection cn = new SqlConnection([YourConnectionString]))
{
SqlCommand cmd = null;
try
{
cn.Open();
string[] commands = sqlScript.Split(new string[] { "GO\r\n", "GO ", "GO\t" }, StringSplitOptions.RemoveEmptyEntries);
foreach (string c in commands)
{
cmd = new SqlCommand(c, cn);
cmd.CommandType = CommandType.Text;
cmd.ExecuteNonQuery();
}
}
catch (Exception ex)
{
success = false;
throw new Exception("Failed to execute sql.", ex);
}
finally
{
cn.Close();
}
return success;
}
}