Understanding Autonumber Retrieval in Access
Microsoft Access uses an Autonumber field to generate unique identifiers for each row inserted into a table. In a classic ASP.NET environment that targets Access 2000 or newer, retrieving that identifier is essential when you need to link related data, such as storing a student’s scores after inserting the student record.
Access exposes the last Autonumber value via the system function @@IDENTITY. Unlike SQL Server, Access does not have a separate SCOPE_IDENTITY function, but Jet 4.0 added ANSI‑92 support, allowing the same call to work across sessions. When you execute an INSERT statement, @@IDENTITY returns the value that was generated for the most recent Autonumber field in the current connection. This makes it ideal for web forms that insert a master record and then immediately need that primary key to populate one or more detail records.
Because Access is single‑user in its original design, concurrency issues are usually limited to multiple connections from the same application. However, in a web context, two users might hit the form at the same time. Without careful handling, one user's @@IDENTITY value could be overwritten by another's insert. Using a transaction that wraps both inserts protects the integrity of the data. The transaction ensures that either both inserts succeed or none do, keeping the master and detail tables in sync.
When writing the SQL that follows, it is crucial to use parameterized queries. Access expects positional parameters marked by question marks, not named placeholders. Mixing named parameters with the question‑mark syntax causes the provider to misinterpret the query and can lead to runtime errors. The example below follows best practice by adding parameters in the same order they appear in the SQL string.
For developers familiar with SQL Server, the transition to Access can feel odd. While SQL Server offers IDENT_CURRENT and @@IDENTITY, Access only exposes @@IDENTITY after the insert. The trick is to keep the connection open and read the value immediately after executing the insert. This pattern works consistently across Jet 4.0 and later versions, making the code portable between Access 2000, 2002, 2003, and the Office 2007 file format.
In the example that follows, a simple student management system is built. The user submits a form with a student’s name, address, and a score. The system stores the student in tblStudent and then writes a record in tblScore that references the new student via the Autonumber value. While the example is small, the same pattern scales to larger scenarios such as orders, invoices, or any master–detail relationship.
Beyond the technical details, think about the overall workflow: receive input, validate, insert master, retrieve identity, insert detail, and report success. This order reduces the chance of partial updates and provides a clear path for error handling. By following these steps, you create a reliable foundation for any application that relies on Access as its data store.
Below is a concise diagram that outlines the sequence of operations. The diagram is optional, but visualizing the flow can help new developers understand where @@IDENTITY fits in the process.

Prompt
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="IdentAccess.aspx.cs" Inherits="AccessDemo.IdentAccess" %></p>
<p><%@ Import Namespace="System.Data" %></p>
<p><%@ Import Namespace="System.Data.OleDb" %></p>
<p><html></p>
<p><head></p>
<p>
<title>Student Entry Form</title></p>
<p></head></p>
<p><body></p>
<p>
<form id="Form1" runat="server"></p>
<p>
<asp:Label id="Label1" runat="server" Text="Name:"></asp:Label></p>
<p>
<asp:TextBox id="txtName" runat="server"></asp:TextBox><br /></p>
<p>
<asp:Label id="Label2" runat="server" Text="Address:"></asp:Label></p>
<p>
<asp:TextBox id="txtAddress" runat="server"></asp:TextBox><br /></p>
<p>
<asp:Label id="Label3" runat="server" Text="Score:"></asp:Label></p>
<p>
<asp:TextBox id="txtScore" runat="server"></asp:TextBox><br /></p>
<p>
<asp:Button id="Button1" runat="server" Text="Add" OnClick="Button1_Click" /></p>
<p>
<asp:Label id="lblStatus" runat="server" Text="" /></p>
<p>
</form></p>
<p></body></p>
<p></html></p>
Prompt
using System;</p>
<p>using System.Data;</p>
<p>using System.Data.OleDb;</p>
<p>namespace AccessDemo</p>
<p>{</p>
<p>
public partial class IdentAccess : System.Web.UI.Page</p>
<p>
{</p>
<p>
protected void Button1_Click(object sender, EventArgs e)</p>
<p>
{</p>
<p>
string connString = @"Provider=Microsoft.Jet.OLEDB.4.0;Data Source=|DataDirectory|\db.mdb";</p>
<p>
using (OleDbConnection conn = new OleDbConnection(connString))</p>
<p>
{</p>
<p>
conn.Open();</p>
<p>
OleDbTransaction transaction = conn.BeginTransaction();</p>
<p>
try</p>
<p>
{</p>
<p>
// Insert the student record</p>
<p>
string insertStudentSql = "INSERT INTO tblStudent (Name, Address) VALUES (?, ?)";</p>
<p>
using (OleDbCommand cmdStudent = new OleDbCommand(insertStudentSql, conn, transaction))</p>
<p>
{</p>
<p>
cmdStudent.Parameters.AddWithValue("@Name", txtName.Text);</p>
<p>
cmdStudent.Parameters.AddWithValue("@Address", txtAddress.Text);</p>
<p>
cmdStudent.ExecuteNonQuery();</p>
<p>
}</p>
<p>
// Retrieve the generated StudentID</p>
<p>
int studentId;</p>
<p>
using (OleDbCommand cmdIdentity = new OleDbCommand("SELECT @@IDENTITY", conn, transaction))</p>
<p>
studentId = Convert.ToInt32(cmdIdentity.ExecuteScalar());</p>
<p>
// Insert the score record</p>
<p>
string insertScoreSql = "INSERT INTO tblScore (StudentID, Score) VALUES (?, ?)";</p>
<p>
using (OleDbCommand cmdScore = new OleDbCommand(insertScoreSql, conn, transaction))</p>
<p>
cmdScore.Parameters.AddWithValue("@StudentID", studentId);</p>
<p>
cmdScore.Parameters.AddWithValue("@Score", int.Parse(txtScore.Text));</p>
<p>
cmdScore.ExecuteNonQuery();</p>
<p>
transaction.Commit();</p>
<p>
lblStatus.Text = "The student information has been entered successfully.";</p>
<p>
}</p>
<p>
catch (Exception ex)</p>
<p>
{</p>
<p>
transaction.Rollback();</p>
<p>
lblStatus.Text = "Error: " + ex.Message;</p>
<p>
}</p>
<p>
}</p>
<p>
}</p>
<p>
}</p>
<p>}</p>
- The
OleDbTransaction guarantees that both inserts occur together.
- Parameters are added in the same order as the question marks in the SQL strings.
- The
SELECT @@IDENTITY command is executed immediately after the first insert, still inside the same transaction.
- Any exception causes a rollback, preventing a half‑inserted state.
To test the form, run the web application, enter a name, address, and a numeric score, and click Add. The status label will confirm the operation. Inspect the db.mdb file in a tool like Microsoft Access or the SQL Server Management Studio (if you use the Access OLEDB provider) to verify that the student and score rows were created correctly and that the foreign key matches the Autonumber value.
When scaling this approach to more complex scenarios, remember that the transaction boundary can be extended to cover multiple detail inserts or to include additional logic such as audit trail entries. The pattern remains the same: open a connection, begin a transaction, insert the master record, retrieve @@IDENTITY, insert related records, and commit or rollback as needed.
Enhancing Reliability: Transactions, Validation, and Best Practices
Even though the sample code uses a transaction, there are still aspects to consider in a production environment. First, input validation is essential. The code assumes that txtScore contains a valid integer, but a malicious user could submit text that breaks int.Parse. Adding server‑side validation before the database calls reduces the risk of exceptions and improves user experience. For example, use int.TryParse and return an error message if the score is not numeric.
Second, guard against SQL injection. The code uses parameterized queries, which already protect against injection for the fields used in the INSERT statements. However, if any part of the SQL string is constructed from user input - such as dynamic table names or column lists - extra care is required. Stick to parameterization wherever possible and avoid string concatenation.
Third, consider concurrency. While the transaction protects the logical sequence of operations, Access may still lock the table when multiple concurrent writes occur. If the application anticipates high traffic, evaluate using a more robust database engine like SQL Server Express, which offers better concurrency control. Alternatively, implement retry logic around the transaction: if a lock exception occurs, pause briefly and attempt the transaction again a limited number of times.
Fourth, implement logging. Catching exceptions is not enough; record the error details in a log file or database table. This practice aids troubleshooting and provides insight into patterns of failure. A simple Trace.WriteLine call or a structured logging framework can capture the exception message, stack trace, and user input that caused the failure.
Fifth, adopt a consistent naming convention for database objects. In the example, the primary key is named StudentID, while the foreign key in tblScore is named StudentID as well. Consistent naming reduces confusion, especially when writing complex queries that join multiple tables. It also improves readability for new developers who join the project.
Sixth, unit test the data access layer. Extract the database logic into a separate class or repository, and write unit tests that mock the database or use an in‑memory Access file. This approach ensures that changes to the SQL do not break existing functionality and gives confidence when refactoring the code.
Finally, document the workflow for future maintenance. Include a diagram that shows the flow from form submission to the final database state, annotate key SQL statements, and note the purpose of the transaction. A clear, well‑maintained document reduces onboarding time and prevents accidental breaks when the business logic evolves.
By layering these best practices - validation, parameterization, retry logic, logging, naming conventions, testing, and documentation - you elevate a simple sample into a robust, production‑ready component. The same pattern applies to any Access‑based application that relies on Autonumber values for relational integrity.
No comments yet. Be the first to comment!