Two users attempt do to the same thing at the same time - mysql

I'm writing a program using visual basic 2015 and MySql database and I asked this question somewhere else before but they couldn't help me.
For example we have a table named "users" :
username | coins
user1 | 3
user2 | 5
I want to change the coin value and this code is working fine but in this code : 1- I get the column's value. 2- I add a unit to the value. 3- I put the value in column. So the problem is what if the database is online and two different computers run the program and try to change the value at the same time? For example one of them is trying to give user1 2 coins and the other one is trying to give user1 4 coins. Then they click on add coin at the same time. First computer gets the value and it is 3. Second Computer gets the value and it is 3. First computer add 2 coins (3+2=5) and put 5 in the column. Now second computer add 4 coins (3+4=7) and put 7 in the column. So we have 7 in our column but we should have 9 because 3+2+4=9
So here is the question : Is there a way to add a value directly to a column or is there another way to solve this problem?
MysqlConn = New MySqlConnection
MysqlConn.ConnectionString = "Server='" & TextBox_MYSQL_Host.Text & "';UserID='" & TextBox_MYSQL_Username.Text & "';Password='" & TextBox_MYSQL_Password.Text & "';Database='" & TextBox_MYSQL_Database.Text & "';Character Set=utf8"
Try
MysqlConn.Open()
DataAdptr = New MySqlDataAdapter
DataTable = New DataTable
MysqlComd = New MySqlCommand
With MysqlComd
.Connection = MysqlConn
.CommandText = "Select * FROM users WHERE username ='" & user1.text & "';"
End With
DataAdptr.SelectCommand = MysqlComd
DataAdptr.Fill(DataTable)
If DataTable.Rows.Count = 0 Then
' Error!
Else
Dim gold As Integer = DataTable.Rows(0).Item("coins").ToString()
gold = gold + 1
MysqlComd = New MySqlCommand
MysqlComd.Connection = MysqlConn
MysqlComd.CommandText = "UPDATE users SET coins='" & gold & "' WHERE username='" & user1.Text & "'"
MysqlComd.ExecuteNonQuery()
End If
MysqlConn.Close()
Catch ex As MySqlException
' Connection Error
Finally
MysqlConn.Dispose()
End Try
End Sub

Disregarding the issues with your code as mentioned in the comments, there are two ways to address the issue you're specifically asking about.
Firstly, you can continue to do it the way you are and employ optimistic concurrency. What that does is assume that each user is the only user editing the data and save as though that's the case and then throw an exception if it turns out to not be. Specifically, when a record is saved, the current values in the database are compared with the original data retrieved by that user and the new values will be saved if and only if they match. If they don't, it's up to you to catch the ConcurrencyException that's thrown and do whatever is appropriate. That will usually be retrieving the data again and either asking the user to start editing again or merge the current data with their existing modifications.
The other option is to simply increment what's in the database rather than saving a specific value, e.g.
UPDATE MyTable SET MyColumn = MyColumn + 1 WHERE ID = #ID

Related

Checking if the fund is sufficient

I am currently doing an ATM Management System, I want my program to check if the amount entered does not exceed the account's balance on database. Here is my code:
Dim w As Double
w = Val(txtwithdraw.Text)
adapter = New MySql.Data.MySqlClient.MySqlDataAdapter("SELECT `balance` FROM `jaagbank` WHERE
acctnum = '" & Form1.namebox.Text & "'", con)
dtable.Clear()
adapter.Fill(dtable)
If w > dtable.Rows.Count Then
MsgBox("Insufficient Balance")
txtwithdraw.Clear()
Return
End If
Decimal is a good datatype to used for money. Don't use the vb6 Val(). You can get unexpected results. Put Option Strict and Option Infer on. I used .TryParse to test the input in txtwithdraw. If it returns True, it puts the converted decimal value in WithdrawalAmount.
Keep your data objects local to the method where they are used. Connections and commands need to be closed and disposed. Using...End Using blocks handle this for us even if there is an error.
Always use parameters to avoid sql injection. If the code is running in Form1, do not qualify namebox with Form1. It seems a bit strange that a field named acctnum is not a number but a String containing a name. You will need to check your database for the correct datatype and field size. I had to guess.
Since you are retrieving a single piece of data, you can use .ExecuteScalar which will give you the first column of the first row.
Private Sub OPCode()
Dim WithdrawalAmount As Decimal
If Not Decimal.TryParse(txtwithdraw.Text, WithdrawalAmount) Then
MessageBox.Show("Please enter a valid withdrawal amount.")
Return
End If
Dim Balance As Object
Using con As New MySqlConnection(ConStr),
cmd As New MySqlCommand("SELECT `balance` FROM `jaagbank` WHERE acctnum = #Name;", con)
cmd.Parameters.Add("#Name", MySqlDbType.VarChar, 100).Value = Form1.namebox.Text
con.Open()
Balance = cmd.ExecuteScalar
End Using
If Balance Is Nothing Then
MessageBox.Show("Account not recognized.")
Return
End If
If WithdrawalAmount > CDec(Balance) Then
MsgBox("Insufficient Balance")
txtwithdraw.Clear()
Return
End If
End Sub

How to use MySQL command inside another MySQLReader while loop in VB.net

I have a project list in a table in MYSQL, and each project has different tables. So in my code, first I'm getting the list of projects, and from each project I'm getting data from respective tables.
conn.Open()
cmdl_readuser = New MySqlCommand("SELECT * FROM projectlist where status = 'Active'", conn)
rl_readuser = cmdl_readuser.ExecuteReader()
While rl_readuser.Read()
Projectname = rl_readuser.GetString("ProjectName")
Space_Remove(Projectname)
cmd_listview = New MySqlCommand("SELECT `Process`,AVG(`Risk Factor`) as AvgRisk ,AVG(`Risk Factor After Mitigation`) as AvgRiskafterImp FROM " + sTableName + " ;", conn)
r2_readuser = cmd_listview.ExecuteReader()
While r2_readuser.Read()
ProjectRiskGridView.Rows.Add(r2_readuser("Process").ToString(), r2_readuser("AvgRisk"), r2_readuser("AvgRiskafterImp"))
End While
End While
conn.Close()
I'm getting data from table and put it in DataGridView. Finally, my DataGridView has records as list of Projects, and its respective average risk.
Here when control comes to the second MySQLCommand, it returns back. It doesn't go to next line. I think problem is with 'conn' .

Access Conditional Formatting - Fewer VBA Options over user interface?

The problem is that while the front-end allows 4 or more conditions, when I tried to set conditions using VBA, I ran into an error when setting the 4th condition. In other words, if I only tried to set 3 conditions in the code, then the code worked fine.
I am using MS Access 2010. I need to set conditional formatting for two textboxes on a continuous form. I know that older versions of MS Access allowed only 3 conditions on a textbox, but I know that I can get more conditions in Access 2010. My current application has 4 conditions using the user interface. In my research on this question, one person said that later versions of MS Access allow up to 50 conditions. I could not confirm this either way, even when I reviewed the Access 2010 specifications page. But I know I can at least get more than 3 conditions.
Here is the test code that works for up to 3 records:
Function fApplyConditionFormatNow()
Dim objFormatConds As FormatCondition
Dim i As Integer 'index number for specific format conditions
Dim stSQL As String 'query to get list of categories
Dim rs As DAO.Recordset
i = 0
'clear out just in case FormatConditions accidentially got saved
'with the form at some point.
Me.ID.FormatConditions.Delete
'get a recordset containing the formatting information
'(ie, get RGB values for each category type)
stSQL = "SELECT * FROM tblTestConditionalFormatting;"
fRunSQL stSQL, rs 'fRunSQL is custom code that gets runs stSQL and returns the recordset
'loop through recordset to get conditional formatting values
Do Until rs.EOF
'create a condition on textbox named "ID". The condition will be for
'the Category/Type (TypeNm) that's up now in the recordset.
Set objFormatConds = Me.ID.FormatConditions.Add(acExpression, , "[TypeNm] = '" & rs!TypeNm & "'")
'add formatting for the condition just created.
With Me.ID.FormatConditions(i)
.BackColor = RGB(rs!RGB1, rs!RGB2, rs!RGB3)
End With
i = i + 1
rs.MoveNext
Loop
rs.Close
Set rs = Nothing
End Function
When 4 records are included in the category table : ie, tblTestConditionalFormatting, I get the following error:
"Runtime error 7966: The format condition you specified is greater than the number of format conditions."
So, there appears to be a bug in that the front-end can handle more than 3 conditions but the VBA object can only handle 3 conditions? OR maybe I'm doing something wrong. Has anyone else come across this? Do you have an idea for a work around?
Thank you!!
Instead of With Me.ID.FormatConditions(i), use the FormatCondition object that you just created:
Set objFormatConds = Me.ID.FormatConditions.Add(acExpression, , "[TypeNm] = '" & rs!TypeNm & "'")
'add formatting for the condition just created.
With objFormatConds
.BackColor = RGB(rs!RGB1, rs!RGB2, rs!RGB3)
End With
You don't need i anymore.
I have similar code that goes (simplified) like this:
For i = 1 To 6
lColor = DLookup("Color", "someTable", "p_Color_ID = " & i)
Set objFrc = txtFld.FormatConditions.Add(acExpression, , "[ColorGroup] = '" & i & "'")
objFrc.BackColor = lColor
Next i
Edit
Apparently you can edit the FormatConditions >= 3 if you don't touch 0..2:
https://access-programmers.co.uk/forums/showthread.php?t=271679
Maybe I have something like this... :/

Update local database all modified data to server database

I'm facing a problem when I want to update data from local database to server data, replacing everything that has been modified at local database. I know it might be simple but I got no idea about this, so any help will be appreciate.
In my situation, I want to use a button to upload all modified data to
the server database. Now I'm just using 2 databases at same server to do
testing.
Private Sub btnUp_Click(sender As System.Object, e As System.EventArgs) Handles btnUp.Click
localconn.ConnectionString = lctext
serverconn.ConnectionString = sctext
Try
localconn.Open()
serverconn.Open()
Dim localcmd As New OdbcCommand("select a.acc_id as localid, a.acc_brcid, a.smartcardid, a.acc_created, a.acc_modified as localmodified, b.acd_firstname, b.acd_ic, b.acd_oldic, b.acd_race, b.acd_dob, b.acd_rescity, b.acd_resaddr1, b.acd_telmobile, b.acd_email, b.acd_telwork, b.acd_modified, b.acd_accid from nsk_account a inner join nsk_accountdetail b on a.acc_id = b.acd_accid", localconn)
Dim servercmd As New OdbcCommand("select c.acc_id, c.acc_brcid, a.smartcardid, c.acc_created, c.acc_modified, d.acd_firstname, d.acd_ic, d.acd_oldic, d.acd_race, d.acd_dob, d.acd_rescity, d.acd_resaddr1, d.acd_telmobile, d.acd_email, d.acd_telwork, d.acd_modified, d.acd_accid from nsk_account c inner join nsk_accountdetail d on c.acc_id = d.acd_accid", serverconn)
localcmd.CommandType = CommandType.Text
Dim rdr As OdbcDataReader = localcmd.ExecuteReader()
Dim thedatatable As DataTable = rdr.GetSchemaTable()
'localcmd.Parameters.Add("#localid", OdbcType.Int, "a.acc_id")
'localcmd.Parameters.Add("#localmodified", OdbcType.DateTime, "b.acd_modified")
Dim localid As String
Dim localmodi As String
localcmd.Parameters.AddWithValue("localid", localid)
localcmd.Parameters.AddWithValue("localmodified", localmodi)
For Each localid In thedatatable.Rows
Dim calldata As New OdbcCommand("SELECT acc_modified from nsk_account where acc_id ='" + localid + "'", serverconn)
Dim reader As OdbcDataReader = calldata.ExecuteReader
txtSDate.Text = reader("acc_modified").ToString
If localmodi <= txtSDate.Text Then
'do nothing, proceed to next data
Else
Dim ACCoverwrite As New OdbcCommand("Update nsk_account SET smartcardid = #mykad, acc_created = #created, acc_modified = #modify WHERE acc_id ='" + localid + "'", serverconn)
Dim DEToverwrite As New OdbcCommand("Update nsk_accountdetail SET acd_firstname = #name, acd_ic = #newic, acd_oldic = #oldic, acd_race = #race, acd_dob = #dob, acd_rescity = #city, acd_resaddr1 = #address, acd_telmobile = #phone, acd_email = #email, acd_telwork = #language, acd_modified = #detmodify WHERE acd_accid ='" + localid + "'", serverconn)
ACCoverwrite.ExecuteNonQuery()
DEToverwrite.ExecuteNonQuery()
End If
Next
MessageBox.Show("Upload success", "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning)
Catch ex As Exception
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Warning)
Finally
localconn.Close()
serverconn.Close()
End Try
End Sub
any comment or suggestion will be appreciate.
I hope you mean table by table. I didn't read your code much but you got the idea - you need 2 connections but here where there are 2 distinct ways of doing it.
Way #1 - you can use when amounts of data (how to say it better? - not huge). You can load a DataTable object with data from server and update changed records. You can use DataAdapter and issue CommitChanges - all changed/new rows will be written to server.
NOTE: you need a mechanism that will reliably able to tell which rows are new and modified on your local DB. Are you OK if your PK in local DB will be different than on the server? You need to answer these questions. May be you need a special mechanism for PK locally. For example, add rows using negative PK integers, which will tell you that these rows are new. And use "ModifiedDate", which together with PK will tell if the row needs updating.
Way #2 - use anytime, even with larger amount of data. Take a local row and examine it. If it is new - insert, if it is existing and "DateModified" changed - do update. There are variations of how to do it. You can use SQL MERGE statement, etc.
But these are two major ways - direct row insert/update and disconnected update/mass commit.
Also, you can do it in bulk, using a transaction - update some rows-commit, and start new transaction. This will help if the application being used as you updating it.
I hope these ideas help. If you do what you do, where you have
For Each localid In thedatatable.Rows
I am not sure what localid is. It should be
' prepare command before loop
sql = "Select * From Table where ID = #1"
' you will create parameter for #1 with value coming from
' row("ID")
Dim cmd As New .....
cmd.Parameters.Add(. . . . )
For Each row As DataRow In thedatatable.Rows
cmd.Parameters(0).Value = row("ID") ' prepare command upfront and only change the value
using reader as IDataReader = cmd.ExecuteReader(. . . . )
If Not reader.Read() Then
' This row is not found in DB - do appropriate action
Continue For
Else
' here check if the date matches and issue update
' Better yet - fill some object
End if
end using
' if you fill object with data from your row -here you can verify if
' update needed and issue it
. . . . . .
Next

vba function to return record with most recent date. given another criteria

I need to set the default value for a textbox in an ms access 2010 form. The default value needs to be the most recent date in CommunicationTable where the ClientNumber is the same as the ClientNumber associated with the current record in the form. The code below references the correct ClientNumber, but I am not sure how to get the most recent date. I am concerned that DMax might not be the appropriate function for getting the most recent date. How should I change the following to get the most recent date?
=DMax("DateOfCommunication","[CommunicationTable]","[ClientNumber]= "
& [Forms]![Main]![NavigationSubform].[Form]![ClientNumber] & "")
I realize I should also post the larger function in which the above function is nested:
=DLookUp("[Level]","[CommunicationTable]","DateOfCommunication= "
& DMax("DateOfCommunication","[CommunicationTable]","[ClientNumber]= "
& [Forms]![Main]![NavigationSubform].[Form]![ClientNumber] & ""))
Also, the form itself is bound to CommunicationTable. This VBA function is in the DefaultValue dialog box, which I get into via the property sheet for the text box. So I am not sure that creating a sql query will work in this case.
EDIT:
I have uploaded a stripped down copy of the database which reproduces the problem at this file sharing site.
To locate the code:
1.) Open the CommunicationEntryForm and
2.) open the AfterUpdate() event procedure for the ClientNumber field.
Next, to reproduce the problem:
1.) close `CommunicationEntryForm`
2.) In the Main form(which should already be open), click the View tab to open
the most recent CommunicationForm for any Client you want. Note the Level
number for that Communication.
3.) Click on the Communication tab. This will leave the form and show a list
of CommunicationForms for that Client.
4.) Click the Create New Form button. This will open up CommunicationEntryForm.
The value for Level should be the same as the value you noted in step 1 above.
The problem is that this is blank.
Can someone show me how to fix this problem?
#CodeMed - I did download the database but found you have issues other than what you are describing- such as when you 'add' a new communication you simply overwrite an existing record. I managed to get the result you were looking for, but it just changes the 3 records around. Does your non-sample program actually have the ability to create and add new records? As it is, I just changed your existing code to this:
Private Sub cmdNewCommForm_Click()
Dim cNum As Long
Dim strSQL As String
Dim rs As Recordset
Dim db As Database
Set db = CurrentDb
cNum = Forms!Main!NavigationSubform.Form!ClientNumber
strSQL = "SELECT Top 1 Co.Level AS MaxOfLevel " & _
"FROM CommunicationTable co Where Co.ClientNumber = " & cNum
Set rs = db.OpenRecordset(strSQL)
Forms!Main!NavigationSubform.Form!NavigationSubform.SourceObject = "CommunicationEntryForm"
Forms!Main!NavigationSubform.Form!NavigationSubform!ClientNumber = cNum
Forms!Main!NavigationSubform.Form!NavigationSubform!DateOfCommunication = Date
If rs.RecordCount > 0 Then
Forms!Main!NavigationSubform.Form!NavigationSubform!Level = rs!MaxOfLevel
Else
Forms!Main!NavigationSubform.Form!NavigationSubform!Level = 0
End If
Set rs = Nothing
Set db = Nothing
End Sub
What I would do is first grab the date by doing something like:
Dim db as Database
Dim rec as Recordset
Set db = CurrentDB
Set rec = db.OpenRecordset("SELECT Top 1 DateOfCommunication FROM CommunicationTable WHERE ClientNumber= " & [Forms]![Main]![NavigationSubform].[Form]![ClientNumber] & " ORDER BY DateOfCommunication DESC")
This will get the most recent date. Then, in your above VBA, you can just stick rec(0) in where your calculation was:
Me.MyDateField = DLookUp("[Level]","[CommunicationTable]","DateOfCommunication= #" & rec(0) & "#")
Substitute "MyDateField" with whatever that name of your date field actually is.
I'm pretty sure you need the pound signs (or "hashtags" as the kids call them today...) in order for Access to do the calculation on a date value.