ODP.net Connection Pooling: ClientID, Client Identifier never changes from first user who logs in
Scenario: We have an application that is using Oracle 10g and the latest version of ODP.net within an ASP.net application. We are utilizing the .ClientID WriteOnly property on the OracleConnection object to pass in a specific UserID to the database for auditing purposes. When Connection Pooling is disabled, this works perfectly.
When it is enabled, the first person who logs in (ex: USER1) updates a record and the MODIFIED_BY is USER1, but when a different user heads into the website after, thus grabbing the pooled connection, the MODIFIED_BY is still USER1 despite passing in USER2 to the ClientID.
Our database logic is as follows:
We persist a class in an ASP.net session that has our database connection logic in it. On the initial call, this is our constructor:
Public Sub New(ByVal connection As String, Optional ByVal oracleClientID As String = "")
MyBase.New()
_oracleConnection = New OracleConnection(connection)
_clientID = oracleClientID
End If
End Sub
Here’s the gist of the code to open connection and close, dispose:
Try
_OraCmd = New OracleCommand(command, _oracleConnection)
With _OraCmd
.BindByName = True
.Parameters.Clear()
.CommandType = CommandType.StoredProcedure
_oracleConnection.Open()
If _clientID <> "" Then _oracleConnection.ClientId = _clientID
Dim OraDadpt As New OracleDataAdapter(_OraCmd)
'' Logic to get data
OraDadpt.Fill(ds)
End With
Catch ex As Exception
Throw ex
Finally
ClearParameters()
_OraCm开发者_如何学God.Dispose()
_oracleConnection.Close()
End Try
The thought is that since the connection is pooled, there is an assumed call to a LOGON Trigger that never happens and the Client Identifier is never set again. ORACLE's documentation, however, states that the ClientID is used for exactly what we are trying to do.
Does anyone have any thoughts as to why the SYS_CONTEXT('USERENV', 'CLIENT_IDENTIFIER') is not being set to a new USERID that is passed into the ClientID when connection pooling is used within our .NET application with ODP.net? Is this a database setting, a listener setting?
Update
We forwarded the issue to Oracle. In doing so, we had to create a small test app that mimicked the issue. When doing that, on my localhost -- everything worked perfectly using Visual Studio's built-in Cassini web server. With IIS, the issue occurs.
UPDATE
Determined that IIS wasn't the problem. It was package variables not being cleared out due to connections that were pooled being re-used, in essence, what pooling is supposed to do. We solved this by using DBMS_SESSION.MODIFY_PACKAGE_STATE(DBMS_SESSION.REINITIALIZE).
Try using DBMS.Rest_Package before closing the connections.
The problem I think is with pooling turned on ODP is keeping the connection thus as each user opens and closes a connection with ODP, pooling is keeping the session package variables in memory; until the connection times out. However, since the time/out and re-establish of a connection from the database to the pool only occurs AFTER a connection is RETURNED to the pool, you're operating with someone else's session data.
This works fine both with pooling on and off. The ClientId and ClientInfo on the Oracle Session do not update until a command is executed.
Could you verify your if statement is correct? If _clientID <> "" Then _oracleConnection.ClientId = _clientID. Even when you close the connection, the clientId will still stay the same. Not sure where your setting/getting _clientId when you pass that into your method.
class Program
{
static void Main(string[] args)
{
TestClientId test = new TestClientId();
test.DoSomething("FirstClientId");
test.DoSomething("ChangedClientId");
}
}
public class TestClientId
{
/// <summary>
/// The connection string.
/// </summary>
private const string ConnString = "DATA SOURCE=//server:port/service_name;USER ID=user;PASSWORD=pswd;";
/// <summary>
/// The oracle connection.
/// </summary>
private OracleConnection connection;
/// <summary>
/// The oracle session id.
/// </summary>
private long sid;
/// <summary>
/// Initializes a new instance of the <see cref="TestClientId"/> class.
/// </summary>
public TestClientId()
{
this.connection = new OracleConnection(ConnString);
}
/// <summary>
/// Changes the client id of the oracle connection.
/// </summary>
/// <param name="clientId">The client id.</param>
public void DoSomething(string clientId)
{
this.connection.Open();
this.sid = this.GetSessionId(this.connection);
if (!string.IsNullOrEmpty(clientId))
{
this.connection.ClientInfo = clientId;
this.connection.ClientId = clientId;
}
OracleCommand command = new OracleCommand("select * from dual", this.connection);
command.ExecuteNonQuery();
this.connection.Close();
}
/// <summary>
/// Gets the session id.
/// </summary>
/// <param name="con">The connection object.</param>
/// <returns>The current oracle session id.</returns>
public int GetSessionId(OracleConnection con)
{
OracleCommand cmd = new OracleCommand();
cmd.Connection = con;
cmd.CommandText = "select SYS_CONTEXT('USERENV','SID') from dual";
object sid = cmd.ExecuteScalar();
return Convert.ToInt32(sid);
}
}
The ClientId is reset only on the connection close and if you are closing then its bound to be reset.
Connection pooling helps the database server to timeout an idle session and use the connection to service an active session. The idle logical session remains open, and the physical connection is automatically reestablished when the next request comes from that session. So is the connection actually closed ?
So it would be good to set the Session identifier by DBMS_SESSION.SET_IDENTIFIER
Hope this helps
It seems very dangerous to me to keep a reference to a database connection in your session. The idea of the connection pool is that a request borrows a connection for the duration of the request. When the request completes, the connection goes back to the pool. The connection will then be reused for more requests from different users.
I suspect that all sorts of nasty things happen when you store a connection in your session. You probably use the connection while it's being concurrently used by another request. Or you might get a closed connection because the connectin pool has to decide to close it.
Furthmore, the LOGON trigger is probably only executed when the database connection is created for the first time but is not executed again when the connection is reused for a different request or a different user.
To fix your problem, grab a database connection at the beginning of every request and explicitly set the Client ID (and/or execute the code that's run by the LOGON trigger). Then use this connection for the duration of the request. But don't store it anywhere after the request has finished.
That way, your connection is always properly initialized with the current user's context. And you adhere to the rules of the connection pool.
When connection pooling is enabled, which is good and of course the way to go in an ASP.NET scenario (and in most scenarios in fact), you should not store any db connection. You must open and close connections when you need it.
Here is a link about SQL Server, but it's the same with Oracle that explains it: SqlConnection Class
So the code you need to use when calling Oracle should be something like this, anywhere in your app, whenyou need it:
Using connection As New OracleConnection(connectionString)
connection.Open()
' Do work here; connection closed on following line.
End Using
The thing is: you cannot have connection pooling enabled with specific user information in the connection string. So I suggest you implement your auditing code without using the connection string.
精彩评论