I'm learning some Fluent NHibernate and I've run across the semi-awesome PersistenceSpecification class.
I've set it up in a unit test to verify my mappings and it works great. However, it leaves the record in the database when done. I tried throwing it in a transaction so I can rollback the changes but I get an error:
System.ObjectDisposedException: Cannot access a disposed object.
Object name: 'AdoTransaction'..
Without a transaction I have to figure out the ID's of the record, retrieve them and delete them and that doesn't seem very elegant.
Any thoughts?
EDIT:
Here is the code snippet:
var factory = GetSessionFactory();
using (var session = factory.OpenSession())
using (var transaction = session.BeginTransaction())
{
new PersistenceSpecification<TimePeriod>(session)
.CheckProperty(x => x.EndDate, DateTime.Today)
.VerifyTheMappings();
transaction.Rollback();
}
Try setting the IsolationLevel on the transaction. This snippet worked for me:
using (var trans = _session.BeginTransaction(IsolationLevel.ReadUncommitted))
{
new PersistenceSpecification<Event>(_session)
.CheckProperty(p => p.StartTime, new DateTime(2010, 1, 1))
.VerifyTheMappings();
trans.Rollback();
}
The PersistenceSpecification is usually used with an in-memory database like SQLite, that's why it doesn't roll anything back. I believe there's a constructor overload that takes an ISession instance, have you tried getting a transaction from there then rolling that back after?
I think the issue here is VerifyTheMappings() calls TransactionSave() which does a tx.Commit() to the database. As James indicated, this technique seems to work great for throw away in-memory testing techniques. This would not work in the case of testing mappings against a legacy database.
Setting IsolationLevel.ReadUncommitted will work, but only incidentally, since all it is doing is telling the session that it can read without needing a new transaction (a dirty read, in DBMS parlance) - so Session.Transaction.Commit () doesn't have to commit a database transaction before the verification reads. This also means that it is not necessarily testing what you think it is testing! (I also think this has probably-questionable support amongst non-MS SQL databases). The answer from leebrandt works because of the explicit rollback, not the isolation level (nb. At the time of the answer this helped more than it does now, see note below).
The good news is that the correct way to do this is to just rollback the transaction manually. Session.Transaction is automatically replaced whenever the transaction is committed, so you'll need to hold a reference to it, and you'll have to open one explicitly anyways, since TransactionalSave () checks if the current transaction is active and creates (and disposes!) its own if not. I typically test all of my mappings in the same fixture, where I also verify the factory creation and a few other infrastructural persistence things, so I like the following pattern for this to keep the plumbing down:
class TestFixture {
static ISessionFactory factory = CreateMyFactorySomehowHere();
ISession session;
ITransaction tx;
public void Setup ()
{
session = factory.OpenSession ();
tx = session.BeginTransaction ();
}
public void Cleanup ()
{
tx.Rollback ();
tx.Dispose ();
session.Close ();
}
public void TestAMappingForSomething ()
{
var spec = new PersistenceSpecification<Something> (session);
spec.VerifyTheMappings ();
}
}
Obviously, insert your own test-framework-specific terminology and attributes/annotations wherever, but you get the idea.
I've just now noticed how old this question is: this behavior was fixed in this commit in July 09, to handle existing transactions nicely so that the above works! Clearly this is what you were doing originally anyways.
i think that it's very important to do this testing with your real db, to see that his tables definition r ok, so i'v developed a very simple class that perform a crud test on a mapped entity and roll back at the end;
internal class GenericMappingTesterWithRealDB<T> where T : IIdentifiable
{
public T EntityToTest { get; set; }
public Func<T, object> PerformEntityManipulationBeforeUpdate { get; set; }
public GenericMappingTesterWithRealDB()
{
Assume.That(SessionFactoryProvider.NewSession,Is.Not.Null);
}
public void RunTest()
{
using (ISession session = SessionFactoryProvider.NewSession)
using (ITransaction transaction = session.BeginTransaction())
{
try
{
session.Save(EntityToTest);
var item = session.Get<T>(EntityToTest.ID);
Assert.IsNotNull(item);
if (PerformEntityManipulationBeforeUpdate != null)
{
PerformEntityManipulationBeforeUpdate.Invoke(EntityToTest);
}
session.Update(EntityToTest);
session.Delete(EntityToTest);
session.Save(EntityToTest);
}
catch (Exception e)
{
Assert.Fail(e.Message, e.StackTrace);
}
finally
{
transaction.Rollback();
}
}
}
}
IIdentifiable in my project is the most basic interface of my entities
the class is using the nunit.framework but u can do it with every testing framework u want
sessionfactoryprovider needs to supply the isession obj
here is a sample of use
/// <summary>
/// Testing the mapping of our entities.
/// there must be a server connection for this kind of test.
/// </summary>
[TestFixture]
internal class someMappingTest
{
[Test(Description = "Check the Encoding Profile FluentNHibernate Mapping")]
[Timeout(20000)]
public void checkthatMappingWorks()
{
// creatw the new entity
TestedType testOn = new TestedType();
// set the initialization values
testOn.Name = "TestProfileExecution";
// create the test object
new GenericMappingTesterWithRealDB<TestedType>
{
// assign an entity
EntityToTest = testOn,
// assign new values for update check
PerformEntityManipulationBeforeUpdate =
delegate(TestedType testedTypeBeingTested)
{
return testedTypeBeingTested.Name = "Updateing Test";
}
}.
// call run test to perform the mapping test.
RunTest();
}
}
Related
I am attempting to implement retries on my database connections, which I've gotten working, but it's broken the user-initiated transactions I have as well.
Specifically, I see errors like the following:
The configured execution strategy 'RetryExecutionStrategy' does not support user initiated transactions. See http://go.microsoft.com/fwlink/?LinkId=309381 for additional information.
I would very much like to implement the first workaround from Microsoft, but even the second one would be enough. Despite many attempts from different angles, I've been unable to get things working for transactions. I've tried searching existing questions on StackOverflow (and the rest of the internet, really), but to no avail. Perhaps the solutions I've found elsewhere work for single threads with only one connection at a time, but I'm working on a large project where there are both transactions and non-transactions occurring in no predictable order.
Ultimately, what I need is a way to turn off retries for transactions or a fix to make transactions not crash when retrying is turned on.
What I have for my DbContext:
[DbConfigurationType("MyNamespace.RetryConfiguration","MyNamespace")]
public partial class RepoContext : DbContext {
public RepoContext(string entityConnectionString) : base(entityConnectionString)
{
}
}
My RetryExecutionStrategy:
public class RetryExecutionStrategy : DbExecutionStrategy
{
public RetryExecutionStrategy(int maxRetries, TimeSpan maxDelay)
: base(maxRetries, maxDelay)
{
}
protected override bool ShouldRetryOn(Exception e)
{
return true;
}
}
My RetryConfiguration:
public class RetryConfiguration : DbConfiguration
{
public RetryConfiguration()
{
var executionStrategy = SuspendExecutionStrategy
? (IDbExecutionStrategy)new DefaultExecutionStrategy()
: new RetryExecutionStrategy(3, new TimeSpan(0, 0, 0, 3));
this.SetExecutionStrategy("Devart.Data.PostgreSql", () => executionStrategy);
}
public static bool SuspendExecutionStrategy
{
get
{
return (bool?)CallContext.LogicalGetData("SuspendExecutionStrategy") ?? false;
}
set
{
CallContext.LogicalSetData("SuspendExecutionStrategy", value);
}
}
}
Unless I am missing something, I believe I implemented Microsoft's example correctly. From my understanding of their example, EF should make a new instance of RetryConfiguration for every repo call. Otherwise, their example doesn't make any sense as it would only ever create a connection with RetryExecutionStrategy rather than DefaultExecutionStrategy, which is needed for user-initiated transactions. Contrary to that, I've stuck a breakpoint on the constructor of RetryConfiguration and discovered it's only ever instantiated once.
This has led to a lot of headaches and attempts at fulfilling their second workaround like the following:
var res = new RetryExecutionStrategy(0, new TimeSpan(0,0,0));
RetryConfiguration.SuspendExecutionStrategy = true;
res.Execute(() =>
{
using (var trans = _repoFactory.GetTransactableRepository())
{
trans.BeginTransaction();
var entry = trans.GetAll<MyTable>().First();
entry.Alive = true;
trans.Save();
trans.Commit();
}
});
RetryConfiguration.SuspendExecutionStrategy = false;
I even tried manually calling DefaultExecutionStrategy.Execute().
var des = new System.Data.Entity.Infrastructure.DefaultExecutionStrategy();
des.Execute(() =>
{
using (var trans = _repoFactory.GetTransactableRepository())
{
trans.BeginTransaction();
var entry = trans.GetAll<MyTable>().First();
entry.Alive = true;
trans.Save();
trans.Commit();
}
});
Even in that situation I get an exception saying that RetryConfiguration does not allow user-initiated transactions.
For what it's worth, I tried adding RetryConfiguration.SuspendExecutionStrategy = true/false to our TransactableRepository class's BeginTransaction(), Commit(), and Rollback() functions. The class itself is just a wrapper for the connection. I was unsurprised when it didn't work, given that Microsoft's example only ever shows it being read from on RetryConfiguration's constructor, but I figured it was worth a try since I'm not terribly familiar with CallContext.
I'm working on setting up a new MVC payment site with a dependency-injected database connection in a separate project, and experimenting with some new things as I do. Currently, I'm trying to load an existing transaction from the database, authorize the card payment, and then save the result back to the database. Simple and straightforward, but when I call SaveChanges(), nothing gets saved, and I've run out of things to try.
The database interaction for this is handled by a CheckoutDataProvider:
public class CheckoutDataProvider : ICheckoutDataProvider
{
private readonly CheckoutEntities _context;
public CheckoutDataProvider(CheckoutEntities _context)
{
this._context = _context;
}
public ITransaction GetTransactionDetails(Guid transactionId)
{
var trans = _context.Transactions.FirstOrDefault(x => x.CheckoutTransactionId == transactionId);
return trans; // It's OK if trans == null, because the caller will expect that.
}
public void AddAuthorization(ITransaction transaction, IAuthorizationHistory history)
{
try
{
var trans = (Transaction)transaction;
var hist = (AuthorizationHistory)history;
trans.AuthorizationHistories.Add(hist);
_context.SaveChanges();
}
catch (DbEntityValidationException ex)
{
throw new InvalidDataException(ex.EntityValidationErrors.First().ValidationErrors.First().ErrorMessage, ex);
}
}
}
Transaction and AuthorizationHistory are EF objects and correspond directly to the database tables. CheckoutEntities is the context, as injected by Ninject.
GetTransactionDetails() works flawlessly. I give it the transactionId, I get the object, and then I use that data to run the card and generate the AuthorizationHistory class. Then I call AddAuthorization() to attach it to the transaction and save it to the database. But both the new AuthorizationHistory object and any changes to the original Transaction fail to save.
I can tell from inspecting the _context object that it's not aware of any changes, and if I make changes withing GetTransactionDetails() (before it gets returned as an interface) they will persist. So it looks like a problem with the casting (which makes me feel icky anyway, so I'd love to find out that that's the problem).
Am I missing something obvious? Is there something missing to get this to work? Is there a way to avoid the casting in AddAuthorization()?
Probably you are not sharing the same DBContext Between GetTransactionDetails and AddAuthoritzation. Due to this reason Entity Framework is not able to track the changes.
Set the scope life of DBContext for web request, you can do it with Ninject with .InRequestScope() https://github.com/ninject/ninject/wiki/Object-Scopes , with this option the same DBContext will be used during a web request.
I'm having difficulty making a change to an entity object through a new context. I've had this work plenty of times before, but in this instance I'm getting the old "The ObjectContext instance has been disposed" exception.
Here's my quick edit/save code:
private void SaveChanges()
{
using (var context = new Manticore.ManticoreContext(Global.ManticoreClient))
{
**context.AttachTo("Users", Global.LoggedInUser);**
Global.LoggedInUser.FirstName = this.FirstNameText.Text;
Global.LoggedInUser.LastName = this.LastNameText.Text;
Global.LoggedInUser.Email = this.EmailText.Text;
context.SaveChanges();
}
}
The Global.LoggedInUser property (which is instantiated):
public static Manticore.User LoggedInUser
{
get
{
return HttpContext.Current.Session["LoggedInUser"] as Manticore.User;
}
set
{
HttpContext.Current.Session["LoggedInUser"] = value;
}
}
And the kicker is here's a quick unit test which works (no assert right now, but no exception being thrown):
private User _testUser;
private TestInstanceBucket _testBucket;
[TestInitialize]
public void TestInitialize()
{
using (var context = new Manticore.ManticoreContext())
{
this._testBucket = new TestInstanceBucket(context);
this._testUser = this._testBucket.TestUser;
context.AddObject("Users", this._testUser);
context.SaveChanges();
}
}
[TestMethod]
public void User_ShouldBeAbleToChangeDetails()
{
using (var context = new ManticoreContext())
{
context.AttachTo("Users", this._testUser);
this._testUser.FirstName = "New";
context.SaveChanges();
}
}
Like I say, I've done code like this before and it's been fine. Have I been lucking out, or could storing the entity in the session be causing problems?
Update
I've moved the code from global to a pagebase class which the page using SaveChanges() inherits. Same problem so it rules our static classes/methods and storing the entity in the session somehow causing problems.
Update
So, after several hours of banging my head against a wall, I have a fix that's fairly simple if annoying. After the initial fetch of the user I now call
context.Detach(user);
I can only assume it's something to do with how fast the context is being disposed with garbage collection in ASP.NET compared to in the test environment.
In my EF experience this issue always seems to occur when the entity is still linked to the old context. I believe it is safest to always just re-load the entity using the new context (query on PK). I know it is not the most efficient but it avoids this problem.
I took a look at this page before answering, if you haven't tried some of its suggestions you might give them a go: EF Add/Attach.
I'm using MSTest to run some automated tests against a MySQL 5.5.19 DB via the MySQL Connector and using EntityFramework 4.3.
I'm attempting to use TransactionScope in my DB-accessing class library to perform a rollback when needed. Additionally, in my test code I wish to use TransactionScope to put the DB back into a known state before each test. I use the TestInitialize and TestCleanup methods to accomplish this. Those look like so:
[TestInitialize()]
public void MyTestInitialize()
{
testTransScope = new TransactionScope(TransactionScopeOption.RequiresNew);
}
[TestCleanup()]
public void MyTestCleanup()
{
Transaction.Current.Rollback();
testTransScope.Dispose();
}
Based on the construction of the TransactionScope object there in the initialize function, I believe I should be getting a new transaction scope (there isn't an "ambient" one existing so I believe this ".RequiresNew" isn't technically important here since ".Required" would produce the same result. Since I don't specify a timeout value, it provides me with the default timeout, which I understand to be 60 seconds. Plenty of time for my given test to run.
I've got a function called AddDessert(DessertBiz dessertBizObject) which looks, in part, something like this:
using (var transScope = new TransactionScope(TransactionScopeOption.Required))
{
try
{
// ...
context.Desserts.Add(dessert);
context.SaveChanges();
var dessertId = dessert.Id;
DoOtherDessertStuff(dessertId, dessertBizObject);
transScope.Complete();
}
catch (InvalidOperationException ex)
{
Console.WriteLine(ex.ToString());
}
}
And this function is called by one of my tests.
Since I've specified TransactionScopeOption.Required here, I expect that it will use the "ambient" transaction scope created by the MyTestInitialize function.
My test is arranged to make this DoOtherDessertStuff function to fail and throw an exception, so the call to transScope.Complete(); doesn't happen and the rollback occurs automatically when exiting the using block in the AddDessert function.
The problem I have here is that since it uses the ambient transaction scope created in the MyTestInitialize function, my test Assert calls don't happen because the transaction scope rollback happened - at least this is what I think is happening. I verified that Transaction.Current.TransactionInformation.Statusis TransactionStatus.Aborted, so I feel pretty sure this is what's happening.
Great, so I thought I would change my AddDesert method to look exactly as above except that I would nest a transaction scope rather than using the ambient one, some my using line looks looks like this:
using (var transScope = new TransactionScope(TransactionScopeOption.RequiresNew))
The intent here was that I could nest these transaction scopes, let the rollback in my production code occur and then still check my Asserts in my test code.
But what I am finding is that I get the following error:
System.IO.IOException: Unable to read data from the transport connection: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host failed to respond.
Ideas?
Very good question. When you reference datacontext inside your testmethod after rollback, it will not be available. You need to suppress that. You don't need to specify required option. It is the default option.
Test Method:
[TestMethod()]
public void CreateTestCheckContextCorrectly()
{
MailJobController target = new MailJobController();
target.AddDessert("dessert for Omer");
//With suppress, even if you rollback ambient trans, suppress will ignore ambient trans. You need to reference new context, previous context from controller may be disposed.
using (var suppressscope = new TransactionScope(TransactionScopeOption.Suppress))
{
var newdbcontextref = new DbEntities();
int recordcount = newdbcontextref.StatusDefinitions.Where(x => x.Name == "dessert for Omer").Count();
Assert.AreEqual(0, recordcount);
}
}
Controller method:
public void AddDessert(string dessert)
{
using (var transScope = new TransactionScope())
{
try
{
// ...
StatusDefinition statusDefinition = new StatusDefinition() {Name = dessert};
db.StatusDefinitions.AddObject(statusDefinition);
db.SaveChanges();
Console.WriteLine("object id:"+statusDefinition.StatusDefinitionId);
throw new Exception("hee hee");
transScope.Complete();
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
}
}
I am going threw my site with nhibernate profiler and I got this message
Alert: Use of implicit transactions is
discouraged
http://nhprof.com/Learn/Alerts/DoNotUseImplicitTransactions
I see they are on every single select statement.
private readonly ISession session;
public OrderHistoryRepo(ISession session)
{
this.session = session;
}
public void Save(OrderHistory orderHistory)
{
session.Save(orderHistory);
}
public List<OrderHistory> GetOrderHistory(Guid Id)
{
List<OrderHistory> orderHistories = session.Query<OrderHistory>().Where(x => x.Id == Id).ToList();
return orderHistories;
}
public void Commit()
{
using (ITransaction transaction = session.BeginTransaction())
{
transaction.Commit();
}
}
Should I be wrapping my GetOrderHistory with a transaction like I have with my commit?
Edit
How would I wrap select statements around with a transaction? Would it be like this? But then "transaction" is never used.
public List<OrderHistory> GetOrderHistory(Guid Id)
{
using (ITransaction transaction = session.BeginTransaction())
{
List<OrderHistory> orderHistories = session.Query<OrderHistory>().Where(x => x.Id == Id).ToList();
return orderHistories;
}
}
Edit
Ninject (maybe I can leverage it to help me out like I did with getting a session)
public class NhibernateSessionFactory
{
public ISessionFactory GetSessionFactory()
{
ISessionFactory fluentConfiguration = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(c => c.FromConnectionStringWithKey("ConnectionString")))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Map>().Conventions.Add(ForeignKey.EndsWith("Id")))
.ExposeConfiguration(cfg => cfg.SetProperty("adonet.batch_size", "20"))
//.ExposeConfiguration(BuidSchema)
.BuildSessionFactory();
return fluentConfiguration;
}
private static void BuidSchema(NHibernate.Cfg.Configuration config)
{
new NHibernate.Tool.hbm2ddl.SchemaExport(config).Create(false, true);
}
}
public class NhibernateSessionFactoryProvider : Provider<ISessionFactory>
{
protected override ISessionFactory CreateInstance(IContext context)
{
var sessionFactory = new NhibernateSessionFactory();
return sessionFactory.GetSessionFactory();
}
}
public class NhibernateModule : NinjectModule
{
public override void Load()
{
Bind<ISessionFactory>().ToProvider<NhibernateSessionFactoryProvider>().InSingletonScope();
Bind<ISession>().ToMethod(context => context.Kernel.Get<ISessionFactory>().OpenSession()).InRequestScope();
}
}
Edit 3
If I do this
public List<OrderHistory> GetOrderHistory(Guid Id)
{
using (ITransaction transaction = session.BeginTransaction())
{
List<OrderHistory> orderHistories = session.Query<OrderHistory>().Where(x => x.Id == Id).ToList();
return orderHistories;
}
}
I get this alert
If I do this
public List<OrderHistory> GetOrderHistory(Guid Id)
{
using (ITransaction transaction = session.BeginTransaction())
{
List<OrderHistory> orderHistories = session.Query<OrderHistory>().Where(x => x.Id == Id).ToList().ConvertToLocalTime(timezoneId);
transaction.Commit();
return orderHistories;
}
}
I can get rid of the errors but can get unexpected results.
For instance when I get orderHistories back I loop through all of them and convert the "purchase date" to the users local time. This is done through an extension method that I created for my list.
Once converted I set it to override the "purchase date" in the object. This way I don't have to create a new object for one change of a field.
Now if I do this conversion of dates before I call the commit nhibernate thinks I have updated the object and need to commit it.
So I am putting a bounty on this question.
How can I create my methods so I don't have to wrap each method in a transaction? I am using ninject already for my sessions so maybe I can leverage that however some times though I am forced to do multiple transactions in a single request.
So I don't know have just one transaction per request is a soultion.
how can I make sure that objects that I am changing for temporary use don't accidentally get commit?
how can I have lazy loading that I am using in my service layer. I don't want to surround my lazy loading stuff in a transaction since it usually used in my service layer.
I am finding it very hard to find examples of how to do it when your using the repository pattern. With the examples everything is always written in the same transaction and I don't want to have transactions in my service layer(it is the job of the repo not my business logic)
The NHibernate community recommends that you wrap everything in a transaction, regardless of what you're doing.
To answer your second question, generally, it depends. If this is a web application, you should look at the session-per-request pattern. In most basic scenarios, what this means is that you'll create a single session per HTTP request in which the session (and transaction) is created when the request is made and committed/disposed of at the end of the request. I'm not saying that this is the right way for you, but it's a common approach that works well for most people.
There are a lot of examples out there showing how this can be done. Definitely worth taking the time to do a search and read through things.
EDIT: Example of how I do the session/transaction per request:
I have a SessionModule that loads the session from my dependency resolver (this is a MVC3 feature):
namespace My.Web
{
public class SessionModule : IHttpModule {
public void Init(HttpApplication context) {
context.BeginRequest += context_BeginRequest;
context.EndRequest += context_EndRequest;
}
void context_BeginRequest(object sender, EventArgs e) {
var session = DependencyResolver.Current.GetService<ISession>();
session.Transaction.Begin();
}
void context_EndRequest(object sender, EventArgs e) {
var session = DependencyResolver.Current.GetService<ISession>();
session.Transaction.Commit();
session.Dispose();
}
public void Dispose() {}
}
}
This is how I register the session (using StructureMap):
new Container(x => {
x.Scan(a => {
a.AssembliesFromApplicationBaseDirectory();
a.WithDefaultConventions();
});
x.For<ISessionFactory>().Singleton().Use(...);
x.For<ISession>().HybridHttpOrThreadLocalScoped().Use(sf => sf.GetInstance<ISessionFactory>().OpenSession());
x.For<StringArrayType>().Use<StringArrayType>();
});
Keep in mind that this is something I've experimented with and have found to work well for the scenarios where I've used NHibernate. Other people may have different opinions (which are, of course, welcome).
Well, i guess you could set a Transaction level that's appropriate for the kind of reads that you perform in your application, but the question is: should it really be required to do that within the application code? My guess is no, unless you have a use case that differs from the default transaction levels that (n)hibernate will apply by configuration.
Maybe you can set transaction levels in your nhibernate config.
Or maybe the settings for the profiler are a bit overzealous? Can't tell from here.
But: Have you tried commiting a read transaction? Should not do any harm.
You're passing the ISession into the repository's constructor, which is good. But that's the only thing I like about this class.
Save just calls session.Save, so it's not needed.
GetOrderHistory appears to be retrieving a single entity by ID, you should use session.Get<OrderHistory>(id) for this. You can put the result into a collection if needed.
The Commit method shouldn't be in a repository.
To answer your questions directly...
How can I create my methods so I don't have to wrap each method in a
transaction? I am using ninject
already for my sessions so maybe I can
leverage that however some times
though I am forced to do multiple
transactions in a single request.
The pattern I recommend is below. This uses manual dependency injection but you could use Ninject to resolve your dependencies.
List<OrderHistory> orderHistories;
var session = GetSession(); // Gets the active session for the request
var repository = new OrderHistory(Repository);
// new up more repositories as needed, they will all participate in the same transaction
using (var txn = session.BeginTransaction())
{
// use try..catch block if desired
orderHistories = repository.GetOrderHistories();
txn.Commit();
}
So I don't know have just one
transaction per request is a soultion.
It's perfectly fine to have multiple transactions in a session. I don't like waiting until the request ends to commit because it's too late to provide good feedback to the user.
how can I make sure that objects that
I am changing for temporary use don't
accidentally get commit?
The only sure way is to use an IStatelessSession. Less sure ways are to Evict the object from the Session or Clear the session. But with NHibernate it's not recommended to modify persistent objects.
how can I have lazy loading that I am using in my service layer. I don't
want to surround my lazy loading stuff
in a transaction since it usually used
in my service layer.
If you're using session-per-request this shouldn't be a problem. But you're right that lazy-loading can happen outside of the transaction. I ignore these warnings. I suppose you could "touch" every child object needed so that lazy loads are in a transaction but I don't bother.
I don't want to have transactions in
my service layer(it is the job of the
repo not my business logic)
I disagree with this. The UI or business logic should manage the transaction. The UI is where the user expresses their intent (save or cancel my changes) and is the natural place to manage the transaction.
Recommended approach is unit of work
(session+transaction) per request.
Sure you can use NInject to manage
session lifecycle, I blogged
recently about similar approach
using Castle Windsor.
Here are 4 options:
Don't change entities temporary
Use stateless session in such cases
Detach objects when you are going to
do temporary change
Rollback transaction
I'd go with first one.
You don't have to worry about lazy loading if you are using session-per-request pattern - it will be executed in same request and wrapped with transaction automatically.