Share via


Multiuser processing

Microsoft Dynamics GP supports multiple users accessing the same table at the same time. This is accomplished through Optimistic Concurrency Control (OCC), a form of record locking that allows multiple users to work in the same tables and access the same rows with minimal restrictions, while helping to ensure data integrity.

To allow multiple users to successfully use Microsoft Dynamics GP while Visual Studio Tools integrations are accessing data, you must choose the type of locking used. You must also handle any error conditions that occur as a result of multiple users working with the same row in a table.

The following table lists the various locking scenarios that can occur. The events listed happen in order from left to right. For example, in the first row User A passively locks a row, then User B passively locks the same row. User A deletes the row, then User B changes the contents of the row and saves the row. The changes User B made will be saved.

 

User A

User B

User A

User B

Result

1

Passively locks a row.

Passively locks the same row.

Deletes the row.

Changes the contents of the row and saves the changes.

The changes User B made will be saved.

2

Passively locks a row.

Passively locks the same row.

Changes the contents of the row and saves the row.

Deletes the row.

The row will be deleted.

3

Passively locks a row.

Passively locks the same row.

Changes a field and saves the row.

Changes a different field and saves the row.

Both changes will be saved.

4

Passively locks a row.

Passively locks the same row.

Changes a field and saves the row.

Changes the same field and attempts to save the row.

User B will get an error indicating the row changed. User B's changes won't be saved.

5

Passively locks a row.

Passively locks the same row.

Deletes the row.

Attempts to delete the row.

User B will get an error indicating the row is missing.

6

Actively locks a row.

Passively locks the same row.

Keeps the active lock.

Attempts to delete the row or change a field and save the row.

User B will get a row locked error. The row won't be deleted or the changes won't be saved.

7

Actively locks a row.

Passively locks the same row.

Deletes the row. The active lock is released.

Changes the row and saves it or deletes the row.

If user B changed the row and saved, the changes will be saved.

If User B attempts to delete the row, User B will get an error indicating the row is missing.

8

Actively locks a row.

Passively locks the same row.

Makes changes and saves the row. The active lock is released.

Changes the row and saves it or deletes the row.

If User B changed the same field as User A, User B will get an error indicating the row changed. User B's changes won't be saved.

If user B changed different fields, the changes will be saved.

If User B deleted the row, the row will be deleted.

9

Passively locks a row.

Actively locks the same row.

Attempts to delete the row or change a field and save the row.

Keeps the active lock.

User A will get a row locked error, even though User B's active lock came later than User A's lock.

10

Actively locks a row.

Attempts to actively lock the same row.

 

 

User B will get a row locked error.

Scenarios 1 through 3 don't produce any errors. To be multiuser compatible, your application should be able to handle scenarios 4 to 10, alerting users that an error occurred, and allowing them to respond appropriately.

Use the error value returned from the various table operations like Save() or Remove() to trap errors so your code can deal with the errors that occur. The following examples show how to trap for multiuser errors when reading, saving and removing records.

Warning: You should not check for multiuser error conditions on tables containing text fields. Text fields are those that store text up to 32,000 characters long. Multiuser error codes are not properly returned for tables containing text fields.

Example 1

The following C# example reads and actively locks the first row in the RM_Sales_WORK table. It uses the value returned from the ChangeFirst() method to handle the Sharing error resulting from the row being actively locked by another user.

// Variable for any table operation error
TableError err;

// Create references to the tables
RmSalesWorkTable SalesWorkTable;
SalesWorkTable = Dynamics.Tables.RmSalesWork;

// Release any existing lock
SalesWorkTable.Release();

// Retrieve the first row and actively lock it
err = SalesWorkTable.ChangeFirst(true);
if (err == TableError.Sharing)
{
    // The row is actively locked by another user
    MessageBox.Show("This row is currently locked by another user.");
}

// Close the table
SalesWorkTable.Close();

Example 2

The following C# example reads the first row of the RM_Customer_MSTR table, changes the Salesperson ID, and attempts to save the changed row. The return value from the Save() method is used to handle an error resulting from the row being changed or actively locked by another user.

// Variable for any table operation error
TableError err;

// Create reference to the table
RmCustomerMstrTable CustomerMasterTable;
CustomerMasterTable = Dynamics.Tables.RmCustomerMstr;

// Release any existing lock
CustomerMasterTable.Release();

// Read the first row in the table
err = CustomerMasterTable.ChangeFirst();

// Change the Salesperson ID
CustomerMasterTable.SalespersonId.Value = "STEVE K.";

// Save the changed row
err = CustomerMasterTable.Save();

// Handle any errors
if (err == TableError.Changed)
{
    // The row was changed by another user.
    MessageBox.Show("This row has been changed by another user. Their changes
    will be overridden.");

    // Re-read the current row to lock it
    CustomerMasterTable.Change();

    // Re-write the value
    CustomerMasterTable.SalespersonId.Value = "STEVE K.";

    // Attempt the save again
    err = CustomerMasterTable.Save();

    if (err != TableError.NoError)
    {
        MessageBox.Show("Customer could not be updated.");
    }
}
else if (err == TableError.Sharing)
{
    MessageBox.Show("This row is actively locked by another user. Changes will
    not be saved.");
}
else if (err != TableError.NoError)
{
    // Some other table error occurred
    MessageBox.Show("An error occured updating the customer: " +
    err.ToString());
}

// Close the table
CustomerMasterTable.Close();

Example 3

The following script reads the last row in the GL_Account_Category_MSTR table and attempts to delete it. The error value returned from the Remove() method is used to handle an error resulting from the row being actively locked or already deleted by another user.

// Variable for any table operation error
TableError err;

// Create reference to the table
GlAccountCategoryMstrTable AccountCategoryMasterTable;
AccountCategoryMasterTable = Dynamics.Tables.GlAccountCategoryMstr;

// Release any lock on the row
AccountCategoryMasterTable.Release();

// Retrieve the last row
err = AccountCategoryMasterTable.ChangeLast();

if (err == TableError.NoError)
{
    // Row was read and can be removed
    err = AccountCategoryMasterTable.Remove();

    if (err == TableError.Sharing)
    {
        // The row is actively locked
        MessageBox.Show("This row is actively locked by another user.
        It cannot be removed.");
    }
    else if (err == TableError.NotFound)
    {
        // The row was removed by another user
        MessageBox.Show("This row was removed by another user.");
    }
    else if (err != TableError.NoError)
    {
        // Some other table error occurred
        MessageBox.Show("An error occured removing the row: " +
        err.ToString());
    }
}
else
{
    // Some other table error occurred
    MessageBox.Show("An error occured retrieving the row: " +
    err.ToString());
}

// Close the table
AccountCategoryMasterTable.Close();