View Full Version : Sessions not being reused when using [Transaction]?
Rubens
05-02-2007, 08:28 AM
Hi,
I've got an ASP.Net application with a Dao class (RoleDao) that inherits HibernateDaoSupport.
I also have a business class called RoleProvider that has a method that looks like this:
[Transaction]
public void DeleteRole(string rolename) {
BasicRole role = _roleDao.GetRoleByName(rolename);
_roleDao.Delete(role);
}
I created a simple aspx page in which the "DeleteRole" method is called (and nothing else), and I keep getting the "Illegal attempt to associate a collection with two open sessions" problem.
Now - from the logs, I can see that each one of the calls to _roleDao creates a new session, which explains what is happening, BUT - what is the "appropriate" way to fix this?
Some more information: I am not using OSIV - if I try to use it, I get errors when I try to do "_roleDao.Save()". Or is there a way to use OSIV with the above code?
By inspecting the code it looks like the HibernateTemplate class should look for a "thread-bound" session to reuse, but I just can't find a way to set that up....
Any ideas? (I tried searching the forums for answers, but as I am pretty new to Spring, I could not translate most of them into code/xml...)
Thanks!
Rubens
boriska
05-02-2007, 12:01 PM
Hi,
I am not using OSIV - if I try to use it, I get errors when I try to do "_roleDao.Save()". Or is there a way to use OSIV with the above code?
what does the error say?
KR,
Boris
Rubens
05-02-2007, 01:21 PM
The error is:
Write operations are not allowed in read-only mode (FlushMode.NEVER) - turn your Session into FlushMode.AUTO or remove 'readOnly' marker from transaction definition
because the OSIV class is by default setting flush mode to "never", as far as I can see.
Mark Pollack
05-02-2007, 02:14 PM
Hi Rubens,
Can you post your .xml configuraiton file? It sounds like you may not be applying the transaction aspect. Take a look in the Spring.Northwind example for the config file DeclarativeServicesAttributeDriven.xml, which you can import as is. The config file that loads it is Services.xml.
Cheers,
Mark
I got this error too, then i solved it by:
1) In web.config file add two line to opne session
<system.web>
......
<httpModules>
<add name="Spring" type="Spring.Context.Support.WebSupportModule, Spring.Web"/>
<add name="OpenSessionInView" type="Spring.Data.NHibernate.Support.OpenSessionInViewMo dule, Spring.Data.NHibernate12"/>
</httpModules>
......
</system.web>
2) In web page code file, use Spring.Web.UI.Page as inherited class
public partial class Web_MyPage : Spring.Web.UI.Page
{
...
}
3) Before you call delete method, you should add those lines
ISessionFactory sessionFactory = (ISessionFactory)Spring.Context.Support.ContextReg istry.GetContext().GetObject("SessionFactory");
ISession session = SessionFactoryUtils.GetSession(sessionFactory, false);
session.FlushMode = NHibernate.FlushMode.Auto;
customerDao.Delete(customer);
4) After delete operation you can change FlushMode back
session.Flush();
session.FlushMode = NHibernate.FlushMode.Never;
But I still have a question. How can i use osiv directly in code? If i remove "OpenSessionInView" from web.config, then try to use osiv in code, it will get me a "no session" error...
Rubens
05-03-2007, 12:22 AM
Hi Mark,
I believe I have the "DeclarativeServicesAttributeDriven.xml" file setup properly as I am getting messages like:
INFO : Candidate advisor [Spring.Transaction.Interceptor.TransactionAttribut eSourceAdvisor] accepted for type [ProjectX.User.Bsl.BasicRoleProvider]
So I guess it must be something else? I apologise as I believe I have not given another piece of maybe important information: The BasicRoleProvider, as the name says is a Role provider, which is being configured using:
WebApplicationContext.Current.ConfigureObject(this , "RoleProvider");
inside its "Initialize" method as suggested in other threads, since the Provider is instantiaded without using Spring.
Hi THE,
Thanks for your suggestion, but I am trying to avoid writing that extra session management code around my Business Layer code - of course, if that is possible, and "best practice"...
Thanks
Rubens
05-03-2007, 12:36 AM
Here is my Services.xml:
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net">
<object id="MembershipProvider" type="ProjectX.User.Bsl.BasicMembershipProvider,ProjectX .User">
<property name="MembershipDao" ref="MembershipDao"/>
</object>
<object id="RoleProvider" type="ProjectX.User.Bsl.BasicRoleProvider,ProjectX.User">
<property name="RoleDao" ref="RoleDao"/>
</object>
<import resource="~/Config/DeclarativeServicesAttributeDriven.xml"/>
</objects>
where DeclarativeServicesAttributeDriven.xml is an exact copy from the Northwind example (and I also tried the filename without "~/Config").
The providers are configured in a separate Dao.xml:
<?xml version="1.0" encoding="utf-8" ?>
<objects xmlns="http://www.springframework.net">
<object type="Spring.Objects.Factory.Config.PropertyPlaceholderC onfigurer, Spring.Core">
<property name="ConfigSections" value="databaseSettings"/>
</object>
<object id="HibernateTransactionManager"
type="Spring.Data.NHibernate.HibernateTransactionManager , Spring.Data.NHibernate12">
<property name="DbProvider" ref="DbProvider"/>
<property name="SessionFactory" ref="SessionFactory"/>
</object>
<object id="RoleDao" type="ProjectX.User.Dao.NHibernate.RoleDao,ProjectX.User .Dao.NHibernate">
<property name="SessionFactory" ref="SessionFactory"/>
</object>
<object id="MembershipDao" type="ProjectX.User.Dao.NHibernate.MembershipDao,Project X.User.Dao.NHibernate">
<property name="SessionFactory" ref="SessionFactory"/>
</object>
</objects>
and finally the session factory and db provider:
<db:dbProvider id="DbProvider"
provider="SqlServer-1.1"
connectionString="Data Source=${db.datasource};Database=${db.database};Us er ID=${db.user};Password=${db.password};Trusted_Conn ection=False"/>
<object id="SessionFactory" type="Spring.Data.NHibernate.LocalSessionFactoryObject, Spring.Data.NHibernate12">
<property name="DbProvider" ref="DbProvider"/>
<property name="MappingAssemblies">
<list>
<value>...</value>
</list>
</property>
<property name="HibernateProperties">
<dictionary>
<entry key="hibernate.connection.provider" value="NHibernate.Connection.DriverConnectionProvider"/>
<entry key="hibernate.dialect" value="NHibernate.Dialect.MsSql2000Dialect"/>
<entry key="hibernate.connection.driver_class" value="NHibernate.Driver.SqlClientDriver"/>
</dictionary>
</property>
</object>
Rubens
05-03-2007, 06:36 AM
Ok I hate replying to myself, but I think I found a solution. Please let me know if this would be a "proper" way to fix this (there's some more discussion about this here (http://forum.springframework.net/showthread.php?t=691) and here (http://forum.springframework.net/showthread.php?t=1822)).
The problem is that as the RoleProvider (or any other Provider) is created outside the Spring container, and the [Transaction] methods were inside it, even calling WebApplicationContext.Current.ConfigureObject was just injecting the dependencies (the DAOs), and not setting up a session that could be reused througout the request - basically because spring had no control over the provider instance.
So - to solve this, here's what I did:
Kept my original RoleProvider as it was, but removed the call to WebApplicationContext.Current.ConfigureObject from its Initialize method;
Created a new 'proxy' class, also a RoleProvider (let's call it RoleProviderProxy) which basically forwards all method/property calls to the original RoleProvider;
In the initialize() method of the proxy class, call WebApplicationContext.Current.GetObject to get the real RoleProvider;
In web.config, reference the proxy provider as the role provider.
It works! (without using OSIV)
In detail, the proxy provider looks like this:
public class RoleProviderProxy : RoleProvider
{
private MyRoleProvider _provider;
public override void Initialize(string name, NameValueCollection conf)
{
_provider = (MyRoleProvider) WebApplicationContext.Current.GetObject("RoleProvider");
_provider.Initialize(name, config);
}
public overide AddUsersToRoles(string[] usernames, string[] roleNames)
{
_provider.AddsersToRoles(usernames, roleNames);
}
.....
}
Comments welcome.
Mark Pollack
05-03-2007, 02:45 PM
Hi,
Yes, this makes sense. When the RoleProvider instance is created outside of the container, via 'new', the transaction aspect is not applied to the object. It is plain old 'new' and as you mention, ConfigureObject only configures properties etc, it doesn't create a proxy class with the transaction advice applied.
[Deleted why not just use DI question]. I see (now) that the RoleProvider is created outside your control. Instead of writing a wrapper class you can create the transaction proxy programatically. I'll post some code shortly for doing this.
It works without the OSIV module since you are not rendering any object in the view that were not already fully loaded as part of the session, which is one of the reasons ot use OSIV.
Cheers,
Mark
Rubens
05-04-2007, 01:18 AM
Thanks Mark, just one thing then: given what you said about OSIV, what is the 'Spring-way' to allow operations like '_userDao.Save(object)' without getting the FlushMode errors?
Rubens
05-04-2007, 05:49 AM
Thanks to boriska, THE and Mark for your help - appreciated - Spring.Net has really been a time-saving framework for me!
My last question for the thread then: Mark, given you mentioned that OSIV would be required in certain scenarios, what is the appropriate way to implement a DAO to avoid the FlushMode errors I indicated before when doing something like "myDao.Save(obj)"?
Cheers
tvering
02-18-2009, 07:37 PM
Hi,
Yes, this makes sense. When the RoleProvider instance is created outside of the container, via 'new', the transaction aspect is not applied to the object. It is plain old 'new' and as you mention, ConfigureObject only configures properties etc, it doesn't create a proxy class with the transaction advice applied.
[Deleted why not just use DI question]. I see (now) that the RoleProvider is created outside your control. Instead of writing a wrapper class you can create the transaction proxy programatically. I'll post some code shortly for doing this.
It works without the OSIV module since you are not rendering any object in the view that were not already fully loaded as part of the session, which is one of the reasons ot use OSIV.
Cheers,
Mark
Hi Mark,
OK, this is the exact issue I'm having with Transactions (declarative) and the Spring.NET PostSharp aspect ... it uses ConfigureObject, and, from what you are saying here, that will not apply the transaction advice.
I've taken a stab at applying the transaction advice programatically, but, have failed ... could you please post an example of how to apply the advice programatically to an object instance configured with ConfigureObject???
Thanks!!!
TJ
~
Erich Eichinger
02-28-2009, 09:20 PM
Hi,
the return value of ConfigureObject() is the advised proxy:
IMyBO realBO = ...
IMyBO proxyBO = (IMyBO)appCtx.ConfigureObject( "myObjectDefinitionName", realBO);
hth,
Erich
tvering
03-03-2009, 03:57 PM
Hi Erich,
Mark's last post to this thread seems to state the opposite ... "ConfigureObject only configures properties etc, it doesn't create a proxy class with the transaction advice applied"
This is what I'm seeing also ... the declarative transactions applied to the class that I am passing to configure are not recognized.
Could it be just an issue with how transaction advice is applied? (I'm using
<tx:attribute-driven/>, and the stock NHibernate20 HibernateTransactionManager).
TJ
~
Erich Eichinger
03-04-2009, 10:54 PM
Hi,
a valid object definition must be defined in the context to have ObjectPostProcessors applied (proxing is usually done by such ObjectPostProcessors. Please see the tests "DefaultListableObjectFactory.ConfigureObjectApplie sobjectPostProcessorsUsingDefinition"
[Test]
public void ConfigureObjectAppliesObjectPostProcessorsUsingDef inition()
{
DefaultListableObjectFactory of = new DefaultListableObjectFactory();
object wrapperObject = "WrapperObject";
of.AddObjectPostProcessor( new TestObjectPostProcessor(wrapperObject));
of.RegisterObjectDefinition("myObjectDefinition", new RootObjectDefinition());
object testObject = "TestObject";
object resultObject = of.ConfigureObject(testObject, "myObjectDefinition");
Assert.AreSame(wrapperObject, resultObject);
}
as you can see this OD just needs to be present - it can e.g. be marked abstract.
hth,
Erich
Powered by vBulletin® Version 4.1.5 Copyright © 2012 vBulletin Solutions, Inc. All rights reserved.