Обновление базы данных (Microsoft Access) с использованием VB.net - PullRequest
0 голосов
/ 04 апреля 2020

Я создаю приложение банкомата для проекта. Я использую VB. net, чтобы обновить баланс доступа MS к новому балансу, чтобы отразить действия функции отзыва в приложении. В настоящее время новый баланс отображается в «txtNewBalance», однако он не обновляется в самой базе данных и, в свою очередь, не может использоваться на отдельной странице депозита, поскольку отображается исходный баланс, а не обновленный баланс после снятия некоторых из них. баланс. Вот мой код:

Public Class frmWithdraw

    Dim objConnection As New OleDb.OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0; Data Source=ATM2.accdb")

    Dim objAccountDA As New OleDb.OleDbDataAdapter("Select * from tblAccount", objConnection)
    Dim objAccountCB As New OleDb.OleDbCommandBuilder(objAccountDA)
    Dim AccountDataSet As New DataSet()

    Public Sub StoreDetails()
        Dim objRow As DataRow

        objRow = AccountDataSet.Tables("tblAccount").Rows.Find("AccounNum")

        objRow.Item("AccountNum") = txtAccountNum.Text
        objRow.Item("AccountBalance") = txtBalance.Text

    End Sub

    Public Sub Retrieve()

        objAccountDA.FillSchema(AccountDataSet, SchemaType.Source, "tblAccount")
        objAccountDA.Fill(AccountDataSet, "tblAccount")

        txtAccountNum.Text = frmLogin.EmployeeNO

        FillAccountDetails()
        'FillUserDetails()
    End Sub

    Private Sub frmWithdraw_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        'TODO: This line of code loads data into the 'ATM2DataSet.tblAccount' table. You can move, or remove it, as needed.
        ' Me.TblAccountTableAdapter.Fill(Me.ATM2DataSet.tblAccount)
        Retrieve()
    End Sub

    Public Sub FillAccountDetails()
        Dim objRow As DataRow
        objRow = AccountDataSet.Tables("tblAccount").Rows.Find(txtAccountNum.Text.ToString)
        txtBalance.Text = objRow.Item("Balance").ToString

    End Sub

    Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
        Me.Hide()
        frmMain.Show()
    End Sub

    Private Sub btnWithdraw_Click(sender As Object, e As EventArgs) Handles btnWithdraw.Click

        If txtOutput.Text = "" Or txtOutput.Text = "0" Or txtOutput.Text >= 300 Then
            MessageBox.Show("Invalid amount")
            txtOutput.Clear()

        End If

        txtNewBal.Text = Val(txtBalance.Text) - Val(txtOutput.Text)
        objAccountDA.Update(AccountDataSet, "tblAccount")
        AccountDataSet.AcceptChanges()
        Dim objCurrentRow As DataRow
        objCurrentRow = AccountDataSet.Tables("tblAccount").Rows.Find(txtAccountNum.Text.ToString)
        objCurrentRow("Balance") = Val(txtNewBal.Text.ToString)

        Retrieve()
        MessageBox.Show("Withdrawal Confirmed")

        Me.Hide()
        frmMain.Show()

    End Sub

    Private Sub TblAccountBindingNavigatorSaveItem_Click(sender As Object, e As EventArgs)
        Me.Validate()
        Me.TblAccountBindingSource.EndEdit()
        Me.TableAdapterManager.UpdateAll(Me.ATM2DataSet)

    End Sub

    Private Sub btn20_Click(sender As Object, e As EventArgs) Handles btn20.Click
        txtOutput.Text = 20 * numAmount.Text
    End Sub

    Private Sub btn40_Click(sender As Object, e As EventArgs) Handles btn40.Click
        txtOutput.Text = 40 * numAmount.Text
    End Sub

    Private Sub btn60_Click(sender As Object, e As EventArgs) Handles btn60.Click
        txtOutput.Text = 60 * numAmount.Text
    End Sub

    Private Sub btn80_Click(sender As Object, e As EventArgs) Handles btn80.Click
        txtOutput.Text = 80 * numAmount.Text
    End Sub

    Private Sub btn100_Click(sender As Object, e As EventArgs) Handles btn100.Click
        txtOutput.Text = 100 * numAmount.Text
    End Sub

    Private Sub HelpToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles HelpToolStripMenuItem.Click
        Me.Hide()
        frmHelp.Show()
    End Sub

End Class

Ответы [ 2 ]

1 голос
/ 04 апреля 2020

В настоящее время новый баланс отображается в «txtNewBalance», однако он не обновляется в самой базе данных и, в свою очередь, не может использоваться на отдельной странице депозита, поскольку отображается исходный баланс, а не обновленный баланс после снятия части баланса.

Я не вижу txtNewBalance в вашем коде, поэтому я не уверен, как вы получаете и поддерживаете значение этого элемента управления. Я полагаю, что это другая форма, которая может быть немодальной и, возможно, не синхронизированной c с деятельностью, выполняемой в других формах.

Код в целом выглядит не очень безопасно, и я вижу много возможности для go неверны.

Меня беспокоит этот бит кода:

If txtOutput.Text = "" Or txtOutput.Text = "0" Or txtOutput.Text >= 300 Then
    MessageBox.Show("Invalid amount")
    txtOutput.Clear()

End If

Здесь вы проверяете txtOutput, но даже если введенное значение неверно, остальная часть кода, тем не менее, будет выполнена. MessageBox приостанавливает выполнение, но не завершает его. Если вы хотите остановить выполнение, вы должны добавить Exit Sub сразу после txtOutput.Clear(). Я думаю, что это то, что вы хотели. Здесь происходит то, что вы все равно продолжаете и обновляете AccountDataSet, несмотря ни на что, и вы тоже изменяете txtNewBal. Я не уверен насчет окончательного результата, но, скорее всего, он ошибается. Это может быть вашей проблемой, и она может быть не единственной.

На самом деле, эта проверка некорректна, потому что вы тестируете только для 3 указанных c условий, но может произойти еще много: просто подумайте о пробелах или о чем-то еще.


Для дальнейшего уточнения возможных проблем безопасности рассмотрите этот код в функции Retrieve:

txtAccountNum.Text = frmLogin.EmployeeNO

Лучшим способом было бы определить EmployeeNO как глобальную переменную сразу после входа в систему и передать эту переменную как параметр в вашу функцию, а не повторно проверять другую форму для получения этого значения.

Представьте, что после входа в систему злоумышленник возвращается к форме входа и изменяет значение EmployeeNO (предположительно, текстовое поле). Ваша функция Retrieve будет использовать новое значение, которое может быть значением другого сотрудника.

«Обычно», этого не должно происходить. Но вы используете функцию Show для своих форм, а не ShowDialog, что означает, что ваши формы не являются модальными . Если в вашем коде есть изъян и по какой-то причине Hide действительно не работает, вы можете оказаться в ситуации, когда одновременно видно более одной формы, и можно переключиться с одной формы на другую, чтобы обмануть вас. приложение, чтобы делать плохие вещи.


Другие замечания:

Во всех случаях я настоятельно советую записать все действия в файл. Важно сохранять след активности , особенно когда речь идет о деньгах или реальных товарах.

Будете ли вы вести бизнес с банком, который не может объяснить, где и когда вы сняли деньги с Ваша учетная запись?

Любые изменения должны быть зафиксированы в базе данных немедленно. Не храните изменения в наборе данных дольше, чем это необходимо. Хранить данные в памяти слишком опасно. При отключении питания вы можете потерять данные, и ваши учетные записи могут оказаться в несогласованном состоянии.

Если вам нужно обновить более одной записи, важно использовать транзакций для сохранения целостности ваших данных. Представьте, что вы кредитуете одну учетную запись, но ваш код дает сбой, прежде чем вы сможете вычесть соответствующую сумму из исходной учетной записи. Вы остались с большим расхождением в ваших аккаунтах. Что еще хуже, это неспособность объяснить, откуда возникло несоответствие.

Относительно проверки ввода пользователя в ваш код: действительно ли вы используете обычные текстовые поля? Лучшей альтернативой будет элемент управления NumericUpDown . Он ведет себя как текстовое поле, но ограничивает ввод числами в диапазоне, который вы можете определить заранее. Это также немного улучшает ваш интерфейс.

Вы даже обрабатываете исключения в вашем коде?

Подводя итог, вот некоторые вещи, которые вы, возможно, захотите сделать:

  • улучшить методы проверки
  • выбрать лучшие элементы управления пользовательским интерфейсом для задания
  • log все
  • имеют строгую обработку исключений - если сомневаетесь, остановите свое приложение, а не рискуете сделать что-то не так
  • возможно, прочитайте несколько учебных пособий, потому что многие люди делали подобные вещи, и нет необходимости заново изобретать колесо каждый раз. Только в Stack Overflow имеется множество проверенных кодов, не говоря уже о репозиториях, таких как Github, где вы можете увидеть, как создаются приложения. Вы можете черпать вдохновение из вещей, которые существуют. Я сам многому научился, просто наблюдая за более опытными разработчиками. Важно выучить лучшие практики на ранних этапах, а не вырабатывать вредных привычек , которые всегда будут вас раздражать.

Приложение для банкомата звучит как серьезный материал, особенно если вы собираетесь работать количество людей, и вы не единственный пользователь. Вы не хотите злить их. Вы, конечно, не можете позволить себе быть неряшливым. Так что не торопитесь и документируйте себя. Удачи.

1 голос
/ 04 апреля 2020

ОК, значит, ваш проект имеет строго типизированный набор данных с именем ATM2DataSet, поэтому я упорядочу ваш код для его использования; не используйте слабо типизированные наборы данных, если вы используете строго типизированные - в этом нет необходимости. Строго типизированные лучше и проще в использовании

Вот что я имею в виду:

Me.ATM2DataSet.tblAccount(0).Balance += 100 'strongly typed

'weakly typed, not good
Me.someDataSetIMadeJustNow.Tables("tblAccount").Rows(0).Item("Balance")= DirectCast(Me.someDataSetIMadeJustNow.Tables("tblAccount").Rows(0).Item("Balance"), Integer) + 100 

С набором данных со слабым типом вы получаете имена таблиц и столбцов по строкам или по номерам c по индексам и вы получите возвращенные типы объектов. Благодаря строго типизированному набору данных у вас есть именованные свойства (tblAccount, Balance), которые являются правильным типом данных уже без приведения

Public Class frmWithdraw

    'don't need any of those things you dim'd


    Public Sub PopulateTextBoxes()

        Me.tblAccountTableAdapter.Fill(Me.ATM2DataSet.tblAccount)

        Dim ro = Me.ATM2DataSet.tblAccount.FindByAccountNum(txtAccountNum.Text)

        If ro IsNot Nothing Then ro.Balance = Convert.ToInt32(txtBalance.Text)

    End Sub

    Private Sub frmWithdraw_Load(sender As Object, e As EventArgs) Handles MyBase.Load

        txtAccountNum.Text = frmLogin.EmployeeNO

        txtAccountNum.ReadOnly = true ' stop problems if the user edits it

        PopulateTextBoxes()

    End Sub

    Private Sub btnCancel_Click(sender As Object, e As EventArgs) Handles btnCancel.Click
        Me.Hide()
        frmMain.Show()
    End Sub

    Private Sub btnWithdraw_Click(sender As Object, e As EventArgs) Handles btnWithdraw.Click

        'parse the amount the user wants to withdraw (in output.Text)
        Dim amount as Integer

        'if you use a numericUpDown with a min of 0 and max of 300 it makes this a LOT easier - no more invalid amounts or parsing!
        If Not Integer.TryParse(txtOutput.Text, ByRef amount) OrElse amount < 0 OrElse amount >= 300  Then

            MessageBox.Show("Invalid amount")
            txtOutput.Clear()
            Return
        End If


        Dim ro = Me.ATM2DataSet.FindByAccountNum(txtAccountNum.Text)

        If amount > ro.Balance Then 
          'overdraft? deny?
        End If

        ro.Balance -= amount 'decrement balance

        Me.TblAccountTableAdapter.Update(ro) 'save to db

        MessageBox.Show("Withdrawal Confirmed")

        Me.Hide()
        frmMain.Show()

    End Sub


    'you should turn on Option Strict, and not treat strings and numbers the same
    Private Sub btn20_Click(sender As Object, e As EventArgs) Handles btn20.Click
        txtOutput.Text = (20 * Convert.ToInt32(numAmount.Text)).ToString()
    End Sub

    Private Sub btn40_Click(sender As Object, e As EventArgs) Handles btn40.Click
        txtOutput.Text = (40 * Convert.ToInt32(numAmount.Text)).ToString()
    End Sub

    Private Sub btn60_Click(sender As Object, e As EventArgs) Handles btn60.Click
        txtOutput.Text = (60 * Convert.ToInt32(numAmount.Text)).ToString()
    End Sub

    Private Sub btn80_Click(sender As Object, e As EventArgs) Handles btn80.Click
        txtOutput.Text = (80 * Convert.ToInt32(numAmount.Text)).ToString()
    End Sub

    Private Sub btn100_Click(sender As Object, e As EventArgs) Handles btn100.Click
        txtOutput.Text = (100 * Convert.ToInt32(numAmount.Text)).ToString()
    End Sub

    Private Sub HelpToolStripMenuItem_Click(sender As Object, e As EventArgs) Handles HelpToolStripMenuItem.Click
        Me.Hide()
        frmHelp.Show()
    End Sub

End Class

Теперь я не гарантирую, что у меня есть все поток программы прямо здесь, потому что есть вещи, которые я не знаю, но я пытаюсь продемонстрировать, как использовать набор данных и табличные адаптеры в вашей форме, и показать вам, что создание дополнительных объектов DataSets и OleDbCOmmandXXX совершенно не нужно; Ваша форма уже имеет все, что нужно для чтения, и записала БД с ATM2DataSet и xxxTableAdapter.

Мы загружаем форму, и единственное, что нужно сделать, это поместить accountNumber в соответствующее поле и загрузить остаток

Следующее, что вам нужно посмотреть, это вы (я) загрузите всю базу данных в набор данных, а затем используйте метод FindByXXX для поиска в базе данных по ее первичному ключу. Лучше всего изменить определение запроса в табличном адаптере (откройте файл ATM2Dataset в обозревателе решений) следующим образом (или добавить другой запрос, который делает это):

SELECT * FROM tablAccount WHERE accountNum = ?

Tableadapter Таким образом, в свой метод заполнения будет добавлен другой параметр, в котором вы можете указать номер счета для размещения. Я не знаю, является ли ваш accountNum строкой или целым числом, но после выполнения запроса SQL, способного выполнять поиск по параметрам, он будет выглядеть следующим образом:

'you choose the fill by name during the wizard
Me.tblAccountTableAdapter.FillByAccNum(Me.ATM2DataSet.tblAccount, txtAccountNum.Text)

'or if it's an integer
Me.tblAccountTableAdapter.FillByAccNum(Me.ATM2DataSet.tblAccount, Convert.ToInt32(txtAccountNum.Text))

Это будет загружать только учетная запись, которая вас интересует, вместо всей таблицы БД. Если учетная запись Num является первичным ключом, то будет загружена только одна строка, поэтому вы можете получить к ней доступ по индексу:

Me.ATM2DataSet.tblAccount[0].Balance -= 250 'make a withdraw of 250

Это означает, что вы можете внести соответствующие изменения в метод PopulateTextboxes. Если вам, в конце концов, надоест вводить и выводить данные из текстовых полей, посмотрите привязку к данным. Это очень просто с помощью строго типизированных наборов данных - просто перетащите объекты из окна «Источники данных» (меню «Просмотр» >> «Другие» windows) на форму. Вуаля; появляются текстовые поля, которые связаны с ATM2DataSet, и они сохраняют свои значения в актуальном состоянии / когда вы их вводите, они автоматически изменяют набор данных. Все, что вам нужно, это сохранить результаты. Да, и понимаю, что BindingSource расположен между текстовым полем (которое показывает только одну запись) и набором данных (который содержит несколько записей) и поддерживает концепцию «Текущий»

...