We recently added auditing to our database. A colleague implemented it using triggers and asked me to call a stored procedure on login to the website. The stored procedure inserts the current username, and the current oracle session id in a table so that the trigger could map a session id to a username. Problem is (or was) that he was assuming that a user's internet session mapped to a database session. That is not the case, and we use connection pooling, so oracle session ids can map to many users, not necessarily the user that logged in on that session. So I created a utility method in my data access layer that calls his procedure on every insert, update and delete (ensuring it is in the same transaction):
/// <summary>
/// Performs an insert, update or delete against the database
/// </summary>
/// <param name="transaction"></param>
/// <param name="command">The command.</param>
/// <param name="transaction">A transaction, can be null.
/// No override provided without a transaction, to remind developer to always consider transaction for inserts, updates and deletes</param>
/// <returns>The number of rows affected by the operation</returns>
public static int InsertUpdateDelete(OracleCommand command, OracleTransaction transaction)
{
if (command == null)
throw new ArgumentNullException("command", "command is null.");
OracleConnection connection = null;
bool doCommit = false;
try
{
if (transaction == null)
{
//We always need a transaction for the audit insert
connection = GetOpenConnection();
transaction = connection.BeginTransaction();
doCommit = true;
}
command.Transaction = transaction;
command.Connection = transaction.Connection;
//TODO HttpContext requires that presentation layer is a website. So this call should NOT be in the data access layer.
string username = HttpContext.Current.User.Identity.Name;
if (!String.IsNullOrEmpty(username))
pInsertCurrentUserForAudit(username, command.Transaction);
int recordsAffected = command.ExecuteNonQuery();
if (doCommit)
transaction.Commit();
return recordsAffected;
}
finally
{
if (doCommit)
{
if (transaction != null)
transaction.Dispose();
if (connection != null)
connection.Dispose();
}
}
}
This works and auditing is now working as required. However, I don't like the call to HttpContext:
string username = HttpContext.Current.User.Identity.Name;
It was the quickest way of implementing the task, but I don't think it should be in the Data Access Layer. What if at some unknown time in the future I wanted to access the database using a forms application? Would I get an error when I access HttpContext? Is there a better way of getting to the username that properly separates the concerns? Passing the username in as a parameter to every insert, update and delete is an option, but it will be a lengthy task and I was wondering if there was a more elegant way of doing it.