PDA

View Full Version : How to intercept the connection object when it gets bound to the current transaction



kennethxu
06-19-2008, 05:14 PM
We are using AdoTemplate as well as HibernateTemplate with hibernate transaction manager for our data access needs to Oracle database.

I would like to intercept the connection object so that I can issue an "alter session..." command to populate the client_identifier to be the current ASP.Net user name. Thus, the stored procedures and triggers would know who is the real user.

In Spring Java, I would wrapped the DataSource. How can I do the same in Spring.Net?

Thanks in advance!
Kenneth

Mark Pollack
07-01-2008, 05:04 PM
Hi Kenneth,

In your case it seems like the same wrapping strategy will not work but you can use an AOP based solution to achieve your goal.

As background, Spring.NET has a base class (as in the Java version) DelegatingDbProvider, with some subclasses that allow for the user name and password to be set on a per-request basis or to pick a completely different database instance. See here (http://www.springframework.net/doc-latest/reference/html/dbprovider.html#dbprovider-additional) for some details. These implementations essentially modify the connection string before the connection is open.

If you wrote a wrapper class that would execute the 'alter session' statements after creating the connection in an custom IDbProvider implementation. This would require opening the connection and associating and IDbCommand with a transaction. However, this would happen too soon as the IPlatformTransactionManager implementation hasn't created the connection/transaction pair yet.

The approach to use is to create an AOP interceptor that will be called right after the transaction interceptor. It can retrieve the current connection/transaction pair that is bound to thread local storage and then execute the 'alter select' command.

The Transactions QuickStart (http://www.springframework.net/doc-latest/reference/html/tx-quickstart.html) example shows how to chain interceptors where the pointcut is the [Transaction] attribute in the config file aspects-config.xml

This will essentially looks like the following


<object id="alterSessionInterceptor" type="MyNamespace.AlterSessionInterceptor">
<property name="DbProvider" ref="dbProvider"/>
</object>

<object id="txAttributePointcut" type="Spring.Aop.Support.AttributeMatchMethodPointcut, Spring.Aop">
<property name="Attribute" value="Spring.Transaction.Interceptor.TransactionAttribut e, Spring.Data"/>
</object>

<aop:config>
<aop:advisor order="2"
advice-ref="alterSessionAdvice"
pointcut-ref="txAttributePointcut"/>
</aop:config>
and you need to add the following order statement for declarative transaction management


<tx:attribute-driven order="1"/>
The ordering will then be


Transaction interceptor
Alter session interceptor

The implementation of the AlterSessionInterceptor would roughly be


public sealed class AlterSelectInterceptor : AdoDaoSupport, IMethodBeforeAdvice
{
public virtual void Before(MethodInfo method, object[] args, object target)
{
AdoTemplate.ExecuteNonQuery(CommandType.Text,
"alter session set current_schema=:username",
DbType.String, 0,
CurrentUser);
}

public string CurrentUser { get { //get current asp.net user } }

}
AdoTemplate will use the connection/transaction object just like in your DAO layer.

Let me know how it goes..

Cheers,
Mark

adoehring
07-09-2008, 12:11 PM
Hi Mark,

I have quite the same problem as Kenneth. Our database logic needs a connection context. So for SQL Server before any writing data access takes place I have to execute "set context_info ..,". I followed your example and now I'm in the interceptor function:

public class InitSessionInterceptor : HibernateDaoSupport, IMethodBeforeAdvice {
public virtual void Before(MethodInfo method, object[] args, object target) {

HibernateTemplate.
//AdoTemplate.ExecuteNonQuery(CommandType.Text,
// "set context_info :userid",
// DbType.String, 0,
// "4");
}
}

So the question is now how to do this with HibernateTemplate.

Thank you very much for your help !
Andreas

.ben
07-09-2008, 12:50 PM
Not sure, but can't you use a custom DriverConnectionProvider to do this with NHibernate?

adoehring
07-09-2008, 02:14 PM
Hi .ben

that's it !

Thank's
Andreas

kennethxu
12-23-2008, 07:33 PM
OK, I think I should update this thread as I now get it working perfectly with Oracle ODP.Net. It works well with both Ado and Hibernate; works with or without transaction.

Create a custom IDbProvider that sets the ClientId on the Oracle ODP.Net connection. You can run any SQL in the event handler. Code below is a simplified version with GetUser() logic removed.



using System;
using System.Data;
using Oracle.DataAccess.Client;
using Spring.Data.Common;

namespace MyNamespace
{
public class OracleClientIdDbProvider : DelegatingDbProvider
{
public override IDbConnection CreateConnection()
{
OracleConnection conn = (OracleConnection)
TargetDbProvider.CreateConnection();
conn.StateChange += StateChangeEventHandler;
return conn;
}

private void StateChangeEventHandler(object sender, StateChangeEventArgs e)
{
if(e.OriginalState == ConnectionState.Closed &&
e.CurrentState == ConnectionState.Open)
{
OracleConnection conn = (OracleConnection)sender;
conn.ClientId = GetUser();
}
}
}
}

And here is the configuration section extracted from our spring configuration file.



<object id="DbProvider" type="MyNamespace.OracleClientIdDbProvider">
<property name="TargetDBProvider" ref="TargetDbProvider"/>
</object>

<db:provider id="TargetDbProvider" provider="OracleODP-2.0"
connectionString="${DB.IMM.ConnectionString}"/>

<object id="AdoTemplate" type="Spring.Data.Generic.AdoTemplate, Spring.Data">
<property name="DbProvider" ref="DbProvider"/>
</object>

<object id="SessionFactory"
type="Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate12">
<property name="DbProvider" ref="DbProvider"/>
<property name="MappingAssemblies">
<list>
...
</list>
</property>
<property name="HibernateProperties">
<dictionary>
<entry key="hibernate.dialect" value="${hibernate.dialect}"/>
<entry key="hibernate.connection.driver_class"
value="${hibernate.connection.driver_class}"/>
</dictionary>
</property>
</object>

<object id="HibernateTemplate"
type="Spring.Data.NHibernate.Generic.HibernateTemplate, Spring.Data.NHibernate12">
<property name="SessionFactory" ref="SessionFactory"/>
</object>

<object id="HibernateTransactionManager" name="transactionManager"
type="Spring.Data.NHibernate.HibernateTransactionManager , Spring.Data.NHibernate12">
<property name="DbProvider" ref="DbProvider"/>
<property name="SessionFactory" ref="SessionFactory"/>
</object>


The AOP approach suggested by Mark should work too, but I didn't get a chance to try it out as we are not using AOP for transaction yet.

HTH