McBasic™Get McBasic™ ↗
Build 881
Hands-on tutorial · Data & services

Create, edit and delete persistent records

Complete a CRUD workflow with selection, refresh and export.

1 Form / control designs1 complete BASIC filesGuided sample project

Treat the database write and the refreshed screen as two steps. Read the selected record identity before updating or deleting; never use a visual row position as a primary key. Parameterized commands keep entered values separate from SQL structure. Requery after a successful write so the screen reflects committed data.

Step 1Prepare a safe practice project#

Extract the archive into a new writable folder, separate from your own application. Open CustomerManager.mcproj in McBasic™. Keep every declared source, layout, resource and migration file together.

To reconstruct it yourself, create a DesktopApp project and add the files declared below. In the complete project, follow these steps as a guided rebuild: replace each named source file once and Check after all its procedures are present.

Exact project configuration
CustomerManager.mcproj
{
  "schemaVersion": 1,
  "projectId": "3e098138-bd6b-4d47-b1b0-77fa484c0b82",
  "name": "CustomerManager",
  "kind": "DesktopApp",
  "language": {
    "dialect": "McBasic1",
    "requireExplicit": true,
    "compare": "Binary",
    "defaultArrayBase": 0
  },
  "application": {
    "bundleIdentifier": "com.mcbasic.samples.customermanager",
    "version": "0.1.0",
    "buildNumber": "1",
    "startupFormId": "form-main",
    "shutdownMode": "OnStartupFormClosed"
  },
  "forms": [
    {
      "id": "form-main",
      "typeName": "MainForm",
      "layout": "Forms/MainForm.form.json",
      "code": "Forms/MainForm.bas"
    }
  ],
  "modules": [],
  "classes": [],
  "tests": [],
  "references": [],
  "resources": [],
  "targets": [
    "osx-arm64"
  ],
  "capabilities": []
}

Step 2Build the Form and set its properties#

MainForm

Select the Form first and set its properties. Add containers before their children. These are initial Designer values, not statements to paste into a procedure.

Form propertyValue
Caption"CustomerManager — McBasic"
Width900
Height620
MinWidth760
MinHeight520
TypeNameParent
LabellblNameForm
TextBoxtxtNameForm
LabellblEmailForm
TextBoxtxtEmailForm
CommandButtoncmdSaveForm
CommandButtoncmdDeleteForm
CommandButtoncmdRefreshForm
CommandButtoncmdExportForm
LabellblStatusForm
CommandButtoncmdSeedForm
CommandButtoncmdClearForm
CommandButtoncmdQuitForm
DataGridgrdCustomersForm
lblName · exact Inspector values
PropertyValue
Left24
Top24
Width90
Height30
Caption"Name"
TabIndex0
Anchor edgesLeft, Top
txtName · exact Inspector values
PropertyValue
Left120
Top20
Width250
Height36
Text""
TabIndex1
Anchor edgesLeft, Top
lblEmail · exact Inspector values
PropertyValue
Left390
Top24
Width80
Height30
Caption"Email"
TabIndex2
Anchor edgesLeft, Top
txtEmail · exact Inspector values
PropertyValue
Left470
Top20
Width250
Height36
Text""
TabIndex3
Anchor edgesLeft, Top
cmdSave · exact Inspector values
PropertyValue
Left24
Top72
Width140
Height36
Caption"Save"
TabIndex4
Anchor edgesLeft, Top
cmdDelete · exact Inspector values
PropertyValue
Left176
Top72
Width140
Height36
Caption"Delete"
TabIndex5
Anchor edgesLeft, Top
cmdRefresh · exact Inspector values
PropertyValue
Left328
Top72
Width140
Height36
Caption"Refresh"
TabIndex6
Anchor edgesLeft, Top
cmdExport · exact Inspector values
PropertyValue
Left480
Top72
Width140
Height36
Caption"Export CSV"
TabIndex7
Anchor edgesLeft, Top
lblStatus · exact Inspector values
PropertyValue
Left490
Top116
Width380
Height32
Caption"Starting..."
TabIndex11
Anchor edgesLeft, Top
cmdSeed · exact Inspector values
PropertyValue
Left24
Top112
Width150
Height36
Caption"Load 10,000"
TabIndex8
Anchor edgesLeft, Top
cmdClear · exact Inspector values
PropertyValue
Left186
Top112
Width120
Height36
Caption"Clear"
TabIndex9
Anchor edgesLeft, Top
cmdQuit · exact Inspector values
PropertyValue
Left318
Top112
Width100
Height36
Caption"Quit"
TabIndex10
Anchor edgesLeft, Top
grdCustomers · exact Inspector values
PropertyValue
Left24
Top164
Width846
Height398
TabIndex12
IsReadOnlyTrue
CanUserSortColumnsTrue
CanUserResizeColumnsTrue
HeadersVisibleTrue
Anchor edgesLeft, Top

Step 3Connect the events explicitly#

MainForm

ObjectEventProcedureParameters
cmdSaveClickcmdSave_ClickClick()
cmdDeleteClickcmdDelete_ClickClick()
cmdRefreshClickcmdRefresh_ClickClick()
cmdExportClickcmdExport_ClickClick()
cmdSeedClickcmdSeed_ClickClick()
cmdClearClickcmdClear_ClickClick()
cmdQuitClickcmdQuit_ClickClick()
grdCustomersSelectionChangedgrdCustomers_SelectionChangedSelectionChanged()
FormShownForm_Shown()

Step 4Enter the complete behavior#

Open the named source file. Enter declarations first, then every procedure. Expand a procedure for its full body, or copy the complete file. Helpers are required: do not stop after entering the first Click handler.

Forms/MainForm.bas

Enter these declarations at file scope, before the procedures.

Forms/MainForm.bas · declarations
Option Explicit

Private mDb As Database
Private mRows As BindingSource
Private mSelectedId As Long
Private mSelectedRow As Long
Private mDeleteArmed As Boolean
Private mBusy As Boolean
Private Async Sub Form_Shown()

Initialize state before enabling dependent actions. Keep the shared declarations and startup dependencies with this procedure.

Forms/MainForm.bas · Form_Shown
Private Async Sub Form_Shown()
    Dim migrations As MigrationSet
    Dim version As Long
    Dim ignored As Variant

    mBusy = True
    cmdSave.Enabled = False
    cmdDelete.Enabled = False
    cmdRefresh.Enabled = False
    cmdExport.Enabled = False
    lblStatus.Caption = "Opening SQLite database..."
    Try
        Set mDb = Await Sqlite.OpenAsync(AppPaths.DataFile("customers.db"))
        Set migrations = Sqlite.CreateMigrationSet()
        migrations.Add 1, "CREATE TABLE IF NOT EXISTS Customers (ID INTEGER PRIMARY KEY, Name TEXT NOT NULL, Email TEXT NULL, CreatedAt TEXT NOT NULL)"
        version = Await mDb.ApplyMigrationsAsync(migrations)
        ignored = Await RefreshCustomers()

        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        cmdExport.Enabled = True
        mBusy = False
        lblStatus.Caption = CStr(mRows.RowCount) & " customer(s) ready"
    Catch failure As McException
        mBusy = False
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
    End Try
End Sub
Private Async Sub cmdExport_Click()

Follow the success, failure and cleanup paths together. Do not omit a guard or Finally block when entering this procedure.

Forms/MainForm.bas · cmdExport_Click
Private Async Sub cmdExport_Click()
    Dim command As DbCommand
    Dim rows As Recordset

    If mBusy Then Exit Sub
    If mDb Is Nothing Then Exit Sub
    mBusy = True
    cmdExport.Enabled = False
    lblStatus.Caption = "Exporting customers.csv..."
    Try
        Set command = mDb.CreateCommand("SELECT ID, Name, Email, CreatedAt FROM Customers ORDER BY ID")
        Set rows = Await command.QueryAsync()
        Await rows.ExportCsvAsync(AppPaths.DataFile("customers.csv"))
        lblStatus.Caption = CStr(rows.RowCount) & " customer(s) exported to customers.csv"
    Catch failure As McException
        If failure.Number = 58 Then
            lblStatus.Caption = "customers.csv already exists; rename or remove it before exporting again."
        Else
            lblStatus.Caption = "CSV error " & CStr(failure.Number) & ": " & failure.Message
        End If
    Finally
        mBusy = False
        cmdExport.Enabled = True
    End Try
End Sub
Private Async Function RefreshCustomers() As Boolean

Follow the success, failure and cleanup paths together. Do not omit a guard or Finally block when entering this procedure.

Forms/MainForm.bas · RefreshCustomers
Private Async Function RefreshCustomers() As Boolean
    Dim command As DbCommand
    Dim rows As Recordset

    Try
        Set command = mDb.CreateCommand("SELECT ID, Name, Email, CreatedAt FROM Customers ORDER BY ID")
        Set rows = Await command.QueryAsync()
        Set mRows = rows.Bind()
        mRows.Require "Name"
        Set grdCustomers.DataSource = mRows
        grdCustomers.SelectedIndex = -1
        mSelectedId = 0
        mSelectedRow = -1
        mDeleteArmed = False
        txtName.Text = ""
        txtEmail.Text = ""
        RefreshCustomers = True
    Catch failure As McException
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
        RefreshCustomers = False
    End Try
End Function
Private Async Sub cmdSave_Click()

Follow the success, failure and cleanup paths together. Do not omit a guard or Finally block when entering this procedure.

Forms/MainForm.bas · cmdSave_Click
Private Async Sub cmdSave_Click()
    Dim command As DbCommand
    Dim email As Variant
    Dim wasUpdate As Boolean
    Dim ignored As Variant

    If mBusy Then Exit Sub
    If Len(Trim$(txtName.Text)) = 0 Then
        lblStatus.Caption = "Name is required."
        txtName.SetFocus
        Exit Sub
    End If

    If Len(txtEmail.Text) = 0 Then
        email = Null
    Else
        email = txtEmail.Text
    End If

    mBusy = True
    cmdSave.Enabled = False
    cmdDelete.Enabled = False
    cmdRefresh.Enabled = False
    Try
        wasUpdate = (mSelectedId <> 0)
        If Not wasUpdate Then
            Set command = mDb.CreateCommand("INSERT INTO Customers (Name, Email, CreatedAt) VALUES (@name, @email, datetime('now'))")
            command.AddParameter "@name", txtName.Text
            command.AddParameter "@email", email
        End If

        If wasUpdate Then
            mRows.BeginEdit()
            mRows.SetValue mSelectedRow, "Name", txtName.Text
            mRows.SetValue mSelectedRow, "Email", email
            If Not mRows.Validate() Then
                lblStatus.Caption = mRows.ValidationError
                mRows.CancelEdit()
                mBusy = False
                cmdSave.Enabled = True
                cmdDelete.Enabled = True
                cmdRefresh.Enabled = True
                Exit Sub
            End If
            ignored = Await mRows.SaveAsync(mDb, "Customers", "ID")
        Else
            Await command.ExecuteAsync()
        End If
        ignored = Await RefreshCustomers()
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        If wasUpdate Then
            lblStatus.Caption = "Customer updated."
        Else
            lblStatus.Caption = "Customer saved."
        End If
    Catch failure As McException
        If wasUpdate Then
            mRows.CancelEdit()
        End If
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
    End Try
End Sub
Private Async Sub cmdDelete_Click()

Follow the success, failure and cleanup paths together. Do not omit a guard or Finally block when entering this procedure.

Forms/MainForm.bas · cmdDelete_Click
Private Async Sub cmdDelete_Click()
    Dim ignored As Variant

    If mBusy Then Exit Sub
    If mSelectedId = 0 Then
        lblStatus.Caption = "Select a customer first."
        Exit Sub
    End If
    If Not mDeleteArmed Then
        mDeleteArmed = True
        lblStatus.Caption = "Click Delete again to confirm removal."
        Exit Sub
    End If

    mBusy = True
    cmdSave.Enabled = False
    cmdDelete.Enabled = False
    cmdRefresh.Enabled = False
    Try
        mRows.BeginEdit()
        mRows.Delete mSelectedRow
        ignored = Await mRows.SaveAsync(mDb, "Customers", "ID")
        ignored = Await RefreshCustomers()
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        lblStatus.Caption = "Customer deleted."
    Catch failure As McException
        mRows.CancelEdit()
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
    End Try
End Sub
Private Async Sub cmdRefresh_Click()

Follow the success, failure and cleanup paths together. Do not omit a guard or Finally block when entering this procedure.

Forms/MainForm.bas · cmdRefresh_Click
Private Async Sub cmdRefresh_Click()
    Dim ignored As Variant

    If mBusy Then Exit Sub
    mBusy = True
    cmdSave.Enabled = False
    cmdDelete.Enabled = False
    cmdRefresh.Enabled = False
    Try
        ignored = Await RefreshCustomers()
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        lblStatus.Caption = CStr(mRows.RowCount) & " customer(s) loaded"
    Catch failure As McException
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
    End Try
End Sub
Private Async Sub cmdSeed_Click()

Follow the success, failure and cleanup paths together. Do not omit a guard or Finally block when entering this procedure.

Forms/MainForm.bas · cmdSeed_Click
Private Async Sub cmdSeed_Click()
    Dim command As DbCommand
    Dim ignored As Variant

    If mBusy Then Exit Sub
    mBusy = True
    cmdSave.Enabled = False
    cmdDelete.Enabled = False
    cmdRefresh.Enabled = False
    cmdSeed.Enabled = False
    cmdClear.Enabled = False
    cmdQuit.Enabled = False
    lblStatus.Caption = "Loading 10,000 customers..."
    Try
        Set command = mDb.CreateCommand("WITH digits(n) AS (SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9), seed(n) AS (SELECT d1.n + d2.n * 10 + d3.n * 100 + d4.n * 1000 + 1 FROM digits d1 CROSS JOIN digits d2 CROSS JOIN digits d3 CROSS JOIN digits d4) INSERT INTO Customers (Name, Email, CreatedAt) SELECT 'Customer ' || n, 'customer' || n || '@example.com', datetime('now') FROM seed")
        Await command.ExecuteAsync()
        ignored = Await RefreshCustomers()
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        cmdSeed.Enabled = True
        cmdClear.Enabled = True
        cmdQuit.Enabled = True
        lblStatus.Caption = CStr(mRows.RowCount) & " customer(s) loaded"
    Catch failure As McException
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        cmdSeed.Enabled = True
        cmdClear.Enabled = True
        cmdQuit.Enabled = True
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
    End Try
End Sub
Private Async Sub cmdClear_Click()

Follow the success, failure and cleanup paths together. Do not omit a guard or Finally block when entering this procedure.

Forms/MainForm.bas · cmdClear_Click
Private Async Sub cmdClear_Click()
    Dim command As DbCommand
    Dim ignored As Variant

    If mBusy Then Exit Sub
    mBusy = True
    cmdSave.Enabled = False
    cmdDelete.Enabled = False
    cmdRefresh.Enabled = False
    cmdSeed.Enabled = False
    cmdClear.Enabled = False
    cmdQuit.Enabled = False
    lblStatus.Caption = "Clearing customers..."
    Try
        Set command = mDb.CreateCommand("DELETE FROM Customers")
        Await command.ExecuteAsync()
        ignored = Await RefreshCustomers()
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        cmdSeed.Enabled = True
        cmdClear.Enabled = True
        cmdQuit.Enabled = True
        lblStatus.Caption = CStr(mRows.RowCount) & " customer(s) loaded"
    Catch failure As McException
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        cmdSeed.Enabled = True
        cmdClear.Enabled = True
        cmdQuit.Enabled = True
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
    End Try
End Sub
Private Sub cmdQuit_Click()

Enter the complete action, including its input checks and calls to helper procedures.

Forms/MainForm.bas · cmdQuit_Click
Private Sub cmdQuit_Click()
    App.Quit
End Sub
Private Sub grdCustomers_SelectionChanged()

Read the current value or selection and update its dependent interface. The binding above determines which object calls this handler.

Forms/MainForm.bas · grdCustomers_SelectionChanged
Private Sub grdCustomers_SelectionChanged()
    Dim row As Long
    If mRows Is Nothing Then Exit Sub
    row = CInt(grdCustomers.SelectedIndex)
    If row < 0 Or row >= mRows.RowCount Then Exit Sub
    mSelectedId = CLng(mRows.Value(row, "ID"))
    mSelectedRow = row
    txtName.Text = CStr(mRows.Value(row, "Name"))
    If IsNull(mRows.Value(row, "Email")) Then
        txtEmail.Text = ""
    Else
        txtEmail.Text = CStr(mRows.Value(row, "Email"))
    End If
    mDeleteArmed = False
    lblStatus.Caption = "Editing customer " & CStr(mSelectedId)
End Sub
Copy the complete Forms/MainForm.bas file

Replace the file once; do not append duplicate procedures.

Forms/MainForm.bas
Option Explicit

Private mDb As Database
Private mRows As BindingSource
Private mSelectedId As Long
Private mSelectedRow As Long
Private mDeleteArmed As Boolean
Private mBusy As Boolean

Private Async Sub Form_Shown()
    Dim migrations As MigrationSet
    Dim version As Long
    Dim ignored As Variant

    mBusy = True
    cmdSave.Enabled = False
    cmdDelete.Enabled = False
    cmdRefresh.Enabled = False
    cmdExport.Enabled = False
    lblStatus.Caption = "Opening SQLite database..."
    Try
        Set mDb = Await Sqlite.OpenAsync(AppPaths.DataFile("customers.db"))
        Set migrations = Sqlite.CreateMigrationSet()
        migrations.Add 1, "CREATE TABLE IF NOT EXISTS Customers (ID INTEGER PRIMARY KEY, Name TEXT NOT NULL, Email TEXT NULL, CreatedAt TEXT NOT NULL)"
        version = Await mDb.ApplyMigrationsAsync(migrations)
        ignored = Await RefreshCustomers()

        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        cmdExport.Enabled = True
        mBusy = False
        lblStatus.Caption = CStr(mRows.RowCount) & " customer(s) ready"
    Catch failure As McException
        mBusy = False
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
    End Try
End Sub

Private Async Sub cmdExport_Click()
    Dim command As DbCommand
    Dim rows As Recordset

    If mBusy Then Exit Sub
    If mDb Is Nothing Then Exit Sub
    mBusy = True
    cmdExport.Enabled = False
    lblStatus.Caption = "Exporting customers.csv..."
    Try
        Set command = mDb.CreateCommand("SELECT ID, Name, Email, CreatedAt FROM Customers ORDER BY ID")
        Set rows = Await command.QueryAsync()
        Await rows.ExportCsvAsync(AppPaths.DataFile("customers.csv"))
        lblStatus.Caption = CStr(rows.RowCount) & " customer(s) exported to customers.csv"
    Catch failure As McException
        If failure.Number = 58 Then
            lblStatus.Caption = "customers.csv already exists; rename or remove it before exporting again."
        Else
            lblStatus.Caption = "CSV error " & CStr(failure.Number) & ": " & failure.Message
        End If
    Finally
        mBusy = False
        cmdExport.Enabled = True
    End Try
End Sub

Private Async Function RefreshCustomers() As Boolean
    Dim command As DbCommand
    Dim rows As Recordset

    Try
        Set command = mDb.CreateCommand("SELECT ID, Name, Email, CreatedAt FROM Customers ORDER BY ID")
        Set rows = Await command.QueryAsync()
        Set mRows = rows.Bind()
        mRows.Require "Name"
        Set grdCustomers.DataSource = mRows
        grdCustomers.SelectedIndex = -1
        mSelectedId = 0
        mSelectedRow = -1
        mDeleteArmed = False
        txtName.Text = ""
        txtEmail.Text = ""
        RefreshCustomers = True
    Catch failure As McException
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
        RefreshCustomers = False
    End Try
End Function

Private Async Sub cmdSave_Click()
    Dim command As DbCommand
    Dim email As Variant
    Dim wasUpdate As Boolean
    Dim ignored As Variant

    If mBusy Then Exit Sub
    If Len(Trim$(txtName.Text)) = 0 Then
        lblStatus.Caption = "Name is required."
        txtName.SetFocus
        Exit Sub
    End If

    If Len(txtEmail.Text) = 0 Then
        email = Null
    Else
        email = txtEmail.Text
    End If

    mBusy = True
    cmdSave.Enabled = False
    cmdDelete.Enabled = False
    cmdRefresh.Enabled = False
    Try
        wasUpdate = (mSelectedId <> 0)
        If Not wasUpdate Then
            Set command = mDb.CreateCommand("INSERT INTO Customers (Name, Email, CreatedAt) VALUES (@name, @email, datetime('now'))")
            command.AddParameter "@name", txtName.Text
            command.AddParameter "@email", email
        End If

        If wasUpdate Then
            mRows.BeginEdit()
            mRows.SetValue mSelectedRow, "Name", txtName.Text
            mRows.SetValue mSelectedRow, "Email", email
            If Not mRows.Validate() Then
                lblStatus.Caption = mRows.ValidationError
                mRows.CancelEdit()
                mBusy = False
                cmdSave.Enabled = True
                cmdDelete.Enabled = True
                cmdRefresh.Enabled = True
                Exit Sub
            End If
            ignored = Await mRows.SaveAsync(mDb, "Customers", "ID")
        Else
            Await command.ExecuteAsync()
        End If
        ignored = Await RefreshCustomers()
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        If wasUpdate Then
            lblStatus.Caption = "Customer updated."
        Else
            lblStatus.Caption = "Customer saved."
        End If
    Catch failure As McException
        If wasUpdate Then
            mRows.CancelEdit()
        End If
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
    End Try
End Sub

Private Async Sub cmdDelete_Click()
    Dim ignored As Variant

    If mBusy Then Exit Sub
    If mSelectedId = 0 Then
        lblStatus.Caption = "Select a customer first."
        Exit Sub
    End If
    If Not mDeleteArmed Then
        mDeleteArmed = True
        lblStatus.Caption = "Click Delete again to confirm removal."
        Exit Sub
    End If

    mBusy = True
    cmdSave.Enabled = False
    cmdDelete.Enabled = False
    cmdRefresh.Enabled = False
    Try
        mRows.BeginEdit()
        mRows.Delete mSelectedRow
        ignored = Await mRows.SaveAsync(mDb, "Customers", "ID")
        ignored = Await RefreshCustomers()
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        lblStatus.Caption = "Customer deleted."
    Catch failure As McException
        mRows.CancelEdit()
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
    End Try
End Sub

Private Async Sub cmdRefresh_Click()
    Dim ignored As Variant

    If mBusy Then Exit Sub
    mBusy = True
    cmdSave.Enabled = False
    cmdDelete.Enabled = False
    cmdRefresh.Enabled = False
    Try
        ignored = Await RefreshCustomers()
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        lblStatus.Caption = CStr(mRows.RowCount) & " customer(s) loaded"
    Catch failure As McException
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
    End Try
End Sub

Private Async Sub cmdSeed_Click()
    Dim command As DbCommand
    Dim ignored As Variant

    If mBusy Then Exit Sub
    mBusy = True
    cmdSave.Enabled = False
    cmdDelete.Enabled = False
    cmdRefresh.Enabled = False
    cmdSeed.Enabled = False
    cmdClear.Enabled = False
    cmdQuit.Enabled = False
    lblStatus.Caption = "Loading 10,000 customers..."
    Try
        Set command = mDb.CreateCommand("WITH digits(n) AS (SELECT 0 UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6 UNION ALL SELECT 7 UNION ALL SELECT 8 UNION ALL SELECT 9), seed(n) AS (SELECT d1.n + d2.n * 10 + d3.n * 100 + d4.n * 1000 + 1 FROM digits d1 CROSS JOIN digits d2 CROSS JOIN digits d3 CROSS JOIN digits d4) INSERT INTO Customers (Name, Email, CreatedAt) SELECT 'Customer ' || n, 'customer' || n || '@example.com', datetime('now') FROM seed")
        Await command.ExecuteAsync()
        ignored = Await RefreshCustomers()
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        cmdSeed.Enabled = True
        cmdClear.Enabled = True
        cmdQuit.Enabled = True
        lblStatus.Caption = CStr(mRows.RowCount) & " customer(s) loaded"
    Catch failure As McException
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        cmdSeed.Enabled = True
        cmdClear.Enabled = True
        cmdQuit.Enabled = True
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
    End Try
End Sub

Private Async Sub cmdClear_Click()
    Dim command As DbCommand
    Dim ignored As Variant

    If mBusy Then Exit Sub
    mBusy = True
    cmdSave.Enabled = False
    cmdDelete.Enabled = False
    cmdRefresh.Enabled = False
    cmdSeed.Enabled = False
    cmdClear.Enabled = False
    cmdQuit.Enabled = False
    lblStatus.Caption = "Clearing customers..."
    Try
        Set command = mDb.CreateCommand("DELETE FROM Customers")
        Await command.ExecuteAsync()
        ignored = Await RefreshCustomers()
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        cmdSeed.Enabled = True
        cmdClear.Enabled = True
        cmdQuit.Enabled = True
        lblStatus.Caption = CStr(mRows.RowCount) & " customer(s) loaded"
    Catch failure As McException
        mBusy = False
        cmdSave.Enabled = True
        cmdDelete.Enabled = True
        cmdRefresh.Enabled = True
        cmdSeed.Enabled = True
        cmdClear.Enabled = True
        cmdQuit.Enabled = True
        lblStatus.Caption = "Database error " & CStr(failure.Number) & ": " & failure.Message
    End Try
End Sub

Private Sub cmdQuit_Click()
    App.Quit
End Sub

Private Sub grdCustomers_SelectionChanged()
    Dim row As Long
    If mRows Is Nothing Then Exit Sub
    row = CInt(grdCustomers.SelectedIndex)
    If row < 0 Or row >= mRows.RowCount Then Exit Sub
    mSelectedId = CLng(mRows.Value(row, "ID"))
    mSelectedRow = row
    txtName.Text = CStr(mRows.Value(row, "Name"))
    If IsNull(mRows.Value(row, "Email")) Then
        txtEmail.Text = ""
    Else
        txtEmail.Text = CStr(mRows.Value(row, "Email"))
    End If
    mDeleteArmed = False
    lblStatus.Caption = "Editing customer " & CStr(mSelectedId)
End Sub

Step 5Keep resources and storage reproducible#

No external resource or migration files are declared. Any inline migration, seed data or local JSON is present in the code above. Runtime application data and private preferences are not included in the archive.

Step 6Check, run and test the result#

Save all files and run Check. Resolve diagnostics before Run. Developer CLI users can work from the extracted folder:

Developer CLI
mcbasic check CustomerManager.mcproj
mcbasic run CustomerManager.mcproj

Acceptance checklist

  • Seed the sample records.
  • Create a new customer and reopen the application to verify persistence.
  • Select, edit and save that record.
  • Delete the selected record and verify it is absent after refresh.
  • Export and inspect the output.

Step 7Make one change and repeat the tests#

Add an empty-name guard before the save command creates any SQL command.

Save a working Git checkpoint first. Change one behavior, Check again and repeat the acceptance checklist. Compare with the downloaded solution if a regression appears.

Source baseline 20f4bfed6b · Build manifest