I'm trying to find the best solution to handle transaction in a web application that uses NHibernate.
We use a IHttpModule and at HttpApplication.BeginRequest we open a new session and we bind it to the HttpContext with ManagedWebSessionContext.Bind(context, session); We close and unbind the session on HttpApplication.EndRequest.
In our Repository base class, we always wrapped a transaction around our SaveOrUpdate, Delete, Get methods like, according to best practice:
public virtual void Save(T entity)
{
var session = DependencyManager.Resolve<ISession>();
using (var transaction = session.BeginTransaction())
{
session.SaveOrUpdate(entity);
transaction.Commit();
}
}
But then this doesn't work, if you need to put a transaction somewhere in e.g. a Application service to include several repository calls to Save, Delete, etc..
So what we tried is to use TransactionScope (I didn't want to write my own transactionmanager). To test that this worked, I use an outer TransactionScope that doesn't call .Complete() to force a rollback:
Repository Save():
public virtual void Save(T entity)
{
using (TransactionScope scope = new TransactionScope())
{
var session = DependencyManager.Resolve<ISession>();
session.SaveOrUpdate(entity);
scope.Complete();
}
}
The block that uses the repository:
TestEntity testEntity = new TestEntity { Text = "Test1" };
ITestRepository testRepository = DependencyManager.Resolve<ITestRepository>();
testRepository.Save(testEntity);
using (var scope = new TransactionScope())
{
TestEntity entityToChange = testRepository.GetById(testEntity.Id);
entityToChange.Text = "TestChanged";
testRepository.Save(entityToChange);
}
TestEntity entityChanged = testRepository.GetById(testEntity.Id);
Assert.That(entityChanged.Text, Is.EqualTo("Test1"));
This doesn't work. But to me if NHibernate supports TransactionScope it would! What happens is that there is no ROLLBACK at all in the database but when the testRepository.GetById(testEntity.Id); statement is executed a UPDATE with SET Text = "TestCahgned" is fired instead (It should have been fired between BEGIN TRAN and ROLLBACK TRAN). NHibernate reads the value from the level1 cache and fires a UPDATE to the database. Not expected behaviour!? From what I understand whenever a rollback is done in the scope of NHibernate you also need to close and unbind the current session.
My question is: Does anyone know of a good way to do this using TransactionScope and ManagedWebSessionContext?
I took a very similar approach. In the HttpModule I ask the sessionfactory for a new session + bind it when a new request comes in. But I also begin the transaction here. Then when the request is ending I simply unbind it and attempt to commit the transaction.
Also my base repository doesn't take a session in any way - it instead will ask for the current session and then perform some work with the session. Also I don't wrap anything inside this base class with a transaction. Instead the entire http request is a single unit of work.
This might not be appropriate for the project you are working on, but I prefer this approach because each request will fail or succeed as a single atomic unit. I have a full blog post here with source code if you are interested in the actual implementation.
The below is a sample of what this base repository looks like:
public abstract class NHibernateRepository<T> where T : class
{
protected readonly ISessionBuilder mSessionBuilder;
public NHibernateRepository()
{
mSessionBuilder = SessionBuilderFactory.CurrentBuilder;
}
public T Retrieve(int id)
{
ISession session = GetSession();
return session.Get<T>(id);
}
public void Save(T entity)
{
ISession session = GetSession();
session.SaveOrUpdate(entity);
}
public void Delete(T entity)
{
ISession session = GetSession();
session.Delete(entity);
}
public IQueryable<T> RetrieveAll()
{
ISession session = GetSession();
var query = from Item in session.Linq<T>() select Item;
return query;
}
protected virtual ISession GetSession()
{
return mSessionBuilder.CurrentSession;
}
}
Thanks for the answer!
Yes, it's a simple and straightforward way to solve it. But my problem is that I want to make sure that there is a transaction surrounding a repository operation, even if the application service, repository etc is not called by a web request (other types of clients), therefore I wanted to have a transaction surrounding the lowest level (e.g. session.Save) and then use TransactionScope to create a longer transaction if needed. But your solution is simple and I like that, mabye I'll use that and then make sure that other clients use transactions aswell.
The transaction lifecycle should be:
using (TransactionScope tx = new TransactionScope())
{
using (ISession session1 = ...)
using (ITransaction tx1 = session.BeginTransaction())
{
...do work with session
tx1.Commit();
}
using (ISession session2 = ...)
using (ITransaction tx2 = session.BeginTransaction())
{
...do work with session
tx2.Commit();
}
tx.Complete();
}
You can actually check to see if a transaction is active using: Session.Transaction.IsActive. If one isn't active, you can create one. You could also create a Transact method that does most of this for you automatically. Here's an excerpt that's mostly from NHibernate 3.0 Cookbook:
// based on NHibernate 3.0 Cookbook, Data Access Layer, pg. 192
public class GenericDataAccessObject<TId> : IGenericDataAccessObject<TId>
{
// if you don't want to new up your DAO per Unit-of-work you can
// resolve the session at the time it's accessed.
private readonly ISession session;
protected GenericDataAccessObject(ISession session)
{
this.session = session;
}
protected ISession Session { get { return session; } }
public virtual T Get<T>(TId id)
{
return Transact(() => Session.Get<T>(id));
}
protected virtual void Save<T>(T entity)
{
Transact(() => Session.Save(entity));
}
/// <summary>
/// Perform func within a transaction block, creating a new active transaction
/// when necessary. No error handling is performed as this function doesn't have
/// sufficient information to provide a useful error message.
/// </summary>
/// <typeparam name="TResult">The return type</typeparam>
/// <param name="func">The function wrapping the db operations</param>
/// <returns>The results returned by <c>func</c></returns>
protected TResult Transact<TResult>(Func<TResult> func)
{
// the null Transaction shouldn't happen in a well-behaving Session
// implementation
if (Session.Transaction == null || !Session.Transaction.IsActive)
{
TResult result;
// transaction rollback happens during dispose when necessary
using (var tx = Session.BeginTransaction())
{
result = func.Invoke();
tx.Commit();
}
return result;
// We purposefully don't catch any exceptions as if we were to catch
// the error at this point we wouldn't have enough information to describe
// to the user why it happened -- we could only describe what happened.
}
return func.Invoke();
}
protected void Transact(Action action)
{
Transact<bool>(() =>
{
action.Invoke();
return false;
}
);
}
}
Related
Hello everyone I've successfully implemented the repository pattern with fluentNHibernate but i have one concern abount the ExposeConfiguration when I removed it all the methods works fine but when i adds it ,it resets the tables in the database. so i need you to take a look and give me any notes about the implementation so here is my unit of work class
public class UnitOfWork
{
public static ISession NhibernateHelper()
{
ISessionFactory _sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2012.ConnectionString(#"Data Source=WAAIL-PC\COMPUTERENGINEER;Initial Catalog=TSQL2012;User ID=sa;Password=12345678Ce").ShowSql())
.Mappings(x => x.FluentMappings.AddFromAssemblyOf<UsersMap>())
//.ExposeConfiguration(cfg => new SchemaExport(cfg).Create(false, true)) //when i removed this line it doesn't
//remove the elements from the db
.BuildSessionFactory();
return _sessionFactory.OpenSession();
}}
and here is my repository pattern :
public class Repository<T> :IRepository<T> where T :BaseEntity
{
public Repository(/*IUnitOfWork unitOfWork*/)
{
//_unitOfWork = (UnitOfWork)unitOfWork;
}
public List<T> GetAll()
{
using (ISession Session = UnitOfWork.NhibernateHelper())
{
return Session.Query<T>().ToList();
}
}
public T GetById(int id)
{
using (ISession Session = UnitOfWork.NhibernateHelper())
{
return Session.Get<T>(id);
}
}
public T Insert(T entity)
{
try
{
using (ISession Session = UnitOfWork.NhibernateHelper())
{
using (ITransaction Transaction = Session.BeginTransaction())
{
Session.Save(entity);
Transaction.Commit();
}
}
return entity;
}
catch (Exception)
{
throw;
}
}
public void Update(T entity)
{
using (ISession Session = UnitOfWork.NhibernateHelper())
{
using (ITransaction Transaction = Session.BeginTransaction())
{
Session.Update(entity);
Transaction.Commit();
}
}
}
public void Delete(int id)
{
using (ISession Session = UnitOfWork.NhibernateHelper())
{
using (ITransaction Transaction = Session.BeginTransaction())
{
Session.Delete(Session.Load<T>(id));
Transaction.Commit();
}
}
}
}
here is my usersService
public class UsersService : IUsersService
{
private readonly IRepository<Users> _Repository;
#region Constructor
public UsersService(IRepository<Users> Repository)
{
_Repository = Repository;
}
#endregion
#region Service Implementation
public List<Users> GetListAll()
{
return _Repository.GetAll().ToList();
}
public Users GetById(int Id)
{
return _Repository.GetById(Id);
}
public Users Insert(Users user)
{
return _Repository.Insert(user);
}
public void Update(Users user)
{
_Repository.Update(user);
}
public void Delete(int Id)
{
_Repository.Delete(Id);
}
//public int execute()
//{
// return _Repository.execute();
//}
}
so what i need to know is why the Expose_configuration method causes the tables to reset... and secondly ,Am i moving in the right way in this implementation or not.
if you guys have any update of improvements please tell me ..
best regards.
The evil lies in details
You wrote:
... ExposeConfiguration when I removed it all the methods works fine but when i add it, it resets the tables in the database.
And that's pretty much normal way of operations, since if we look up NHiberate's SchemaExport classes Create method signature, it reads:
public void Create(Action<string> scriptAction, bool execute)
It's documentation states that Create():
Run the schema creation script
And about it parameters:
scriptAction - an action that will be called for each line of the generated ddl.
execute - if the ddl should be executed against the Database.
And you call it as:
.ExposeConfiguration(cfg => new SchemaExport(cfg).Create(false, true))
Your problem lies in that you pass true to the execute parameter, which will -as stated in documentation- execute the schema creation script, which might include drop and rerecration of existing database objects.
Why create demons?
About your second idea, I really see no benefit of adding another layer of indirection to your code, and that's where you summon unneeded demons.
The NHibernate ORM itself is an abstraction itself, why add one more generic one around it? Your Repository<T> just simply calls the already implemented generic NHibernate methods... sort of acting as Proxy to hide NHibernate, if that's intentional please pardon me, but it looks like it's not.
On a final note: your UsersService class first violates the Single responsibility principle and makes your life harder by maintaining and testing it. Second, this class is also not much more than a proxy to call the repository to return values from.
Let me illustrate the problem:
Client calls UsersService.GetById(Id)
Service calls _Repository.GetById(Id)
Your generic abstraction Repository calls NHibernates Session.Get<T>(id)
Do you see the problem?
How easier it would be to say use a pattern that might help you in this case, that is the Command Query Separation.
Where your commands are responsible for writes, and queries about reads and both suits your use-cases.
You can read about my answers earlier about queries and commands.
Currently, this is how I am handling data within my MVC 3 application. Being pretty new to both MVC 3 and the Entity Framework I am not quite sure this is the best approach to handling data within the application. In fact, the call to check UserExists below sometimes gives a SQLConnectionTimeout issue which seems to be completely random. I've tried tracing the problem through SQL profiler and it appears that the timeout occurs right after the connection is being made from EF -> SQL.
I thought I had this solved in another question here on SO but it popped back up, so I wanted to get everyone's opinion on whether or not below is the best way to attempt data handling in my application or is there a better way that may solve the timeout issue.
Here is a link to the other article if it helps: MVC 3/EF/SQL Server strange connection timeout issue
So to summarize my question(s):
Is the code below acceptable?
Should it work fine?
Is there a better way?
Will unnecessary connections to SQL remain open from EF? (SQL Profiler makes it look like it stays open a while even after the using statement has exited)
Any idea on the timeout issue I posted in my other article?
Note: The repository implements IDisposable and has the dispose method listed below. It creates a new instance of the entity context in the repository constructor.
Controller (LogOn using Custom Membership Provider):
if (MembershipService.ValidateUser(model.UserName, model.Password))
{
User newUser = new User();
using (AccountRepository repo = new AccountRepository())
{
newUser = repo.GetUser(model.UserName);
...
}
}
Membership Provider ValidateUser:
public override bool ValidateUser(string username, string password)
{
using (AccountRepository repo = new AccountRepository())
{
try
{
if (string.IsNullOrEmpty(password.Trim()) || string.IsNullOrEmpty(username.Trim()))
return false;
string hash = FormsAuthentication.HashPasswordForStoringInConfigFile(password.Trim(), "md5");
bool exists = false;
exists = repo.UserExists(username, hash);
return exists;
}catch{
return false;
}
}
}
Account Repository Methods for GetUser & UserExists:
Get User:
public User GetUser(string userName)
{
try
{
return entities.Users.SingleOrDefault(user => user.UserName == userName);
}
catch (Exception Ex)
{
throw new Exception("An error occurred: " + Ex.Message);
}
}
User Exists:
public bool UserExists(string userName, string userPassword)
{
if (userName == "" || userPassword == "")
throw new ArgumentException(InvalidUsernamePassword);
try
{
bool exists = (entities.Users.SingleOrDefault(u => u.UserName == userName && u.Password == userPassword) != null);
return exists;
}
catch (Exception Ex)
{
throw new Exception("An error occurred: " + Ex.Message);
}
}
Repository Snippets (Constructor, Dispose etc):
public class AccountRepository : IDisposable
{
private DbContext entities;
public AccountRepository()
{
entities = new DbContext();
}
...
public void Dispose()
{
entities.Dispose();
}
}
Thanks everyone - I realize that this question crit's you for over 9000 with a giant wall of text!
We generally follow the pattern of controlling the instantiation and disposal of the context using an IActionFilter and providing a mechanism to inject that into dependent classes (using Ninject).
If you're not using dependency injection / IoC you can get away with a base controller a little like the following:
using System;
using System.Diagnostics;
using System.Linq;
using System.Transactions;
using System.Web.Mvc;
public class ControllerBase : Controller
{
private ContextState contextState;
protected EntityContext Context
{
get { return this.contextState.Context; }
}
protected TransactionScope TransactionScope
{
get { return this.contextState.TransactionScope; }
}
protected override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
IsolationLevel isolationLevel = filterContext.ActionDescriptor
.GetCustomAttributes(typeof(UnitOfWorkAttribute), false)
.Cast<UnitOfWorkAttribute>()
.Select(a => a.IsolationLevel)
.DefaultIfEmpty(IsolationLevel.ReadCommitted)
.First();
Trace.TraceInformation("Creating database context & transaction scope with isolation {0}.", isolationLevel);
this.contextState = new ContextState
{
TransactionScope = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions { IsolationLevel = isolationLevel }),
Context = new EntityContext()
};
}
protected override void OnActionExecuted(ActionExecutedContext filterContext)
{
base.OnActionExecuted(filterContext);
try
{
if (filterContext.Exception == null)
{
Trace.TraceInformation("Commiting transaction scope.");
this.contextState.TransactionScope.Complete();
}
else
{
Trace.TraceInformation("Rolling back transaction scope.");
}
}
finally
{
try
{
Trace.TraceInformation("Disposing database context.");
this.contextState.Context.Dispose();
}
catch (Exception e)
{
Trace.TraceError("Failed to dispose database context. {0}", e);
}
try
{
Trace.TraceInformation("Disposing transaction scope.");
this.contextState.TransactionScope.Dispose();
}
catch (Exception e)
{
Trace.TraceError("Failed to dispose transaction scope. {0}", e);
}
this.contextState = null;
}
}
private class ContextState
{
public EntityContext Context { get; set; }
public TransactionScope TransactionScope { get; set; }
}
}
/// <summary>
/// Marks an MVC action as requiring a particular <see cref="IsolationLevel" /> when a transaction is
/// created for it.
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class UnitOfWorkAttribute : Attribute
{
private readonly IsolationLevel isolationLevel;
public UnitOfWorkAttribute(IsolationLevel isolationLevel)
{
this.isolationLevel = isolationLevel;
}
/// <summary>
/// Gets an <see cref="IsolationLevel" /> value indicating the isolation level
/// a transaction should use.
/// </summary>
public IsolationLevel IsolationLevel
{
get
{
return this.isolationLevel;
}
}
}
Here we create an instance of your context and a transaction scope just before an action executes and then we cleanup once the action has finished up.
In your derived controller you can then do the following...
public class HomeController : ControllerBase
{
public ActionResult Index()
{
using (AccountRepository accountRepository = new AccountRepository(this.Context))
{
// do stuff
}
return View();
}
}
Passing the context into your repository is a little messy and can be tidied up using something like Ninject to inject the dependency rather than you providing it. http://stevescodingblog.co.uk/dependency-injection-beginners-guide/ provides a pretty reasonable starting point if you're interested.
You can also mark up an action with the UnitOfWorkAttribute to control creation of the transaction used by the context. It's recommended not to use implicit transaction when doing database work (http://nhprof.com/Learn/Alerts/DoNotUseImplicitTransactions) so we always create a transaction scope when executing the action. This has little overhead because, unless the connection is opened, the transaction scope doesn't do much.
Edit: Just to answer another of your questions...
Will unnecessary connections to SQL remain open from EF? (SQL Profiler makes it look like it stays open a while even after the using statement has exited)
Most likely reason here is connection pooling. ADO.NET will maintain open connections for a period of time which makes subsequent calls more efficient because you don't have the latency of opening the connection.
Cheers,
Dean
I am looking into the UoW pattern and have 3 questions.
public class UnitofWork : unitofwork.Models.IUnitofWork
{
private readonly ITransaction transaction;
private readonly ISession session;
public UnitofWork(ISession session)
{
this.session = session;
session.FlushMode = FlushMode.Auto;
transaction = session.BeginTransaction(IsolationLevel.ReadCommitted);
}
public void Commit()
{
if (!transaction.IsActive)
{
throw new InvalidOperationException("Oops! We don't have an active transaction");
}
transaction.Commit();
}
public void Rollback()
{
if (transaction.IsActive)
{
transaction.Rollback();
}
}
public void Dispose()
{
if (session.IsOpen)
{
session.Close();
}
}
}
I just learning about "IsolationLevels" and I am wondering which one should you be using? What happens if you need to make use of multiple "IsolationLevels" for different transactions? How would you configure your UoW(would you make multiple implementations of the above class?)
Other then Rollback and Commit was usually goes into the UoW? I know stuff like creating,updating,getting queries would go into a repository(if your using this pattern) So what else would you typicall see in it?
I copied this UoW from some site(don't have it on hand right now) and made changes it to fit my needs(for instance I am using ninject so I felt there was no point in the UoW taking in the sessionFactory and opening a session in the UoW)
I am wondering what is the Dispose for? I seen this a few times before(some seem to implement IDispose).
I don't actually use it right now in any of my code. I am wondering if it is necessary for me as I mentioned I am using ninject and that handles the session(ie closes it once I am done)
public void Dispose()
{
if (session.IsOpen)
{
session.Close();
}
}
Edit
I added this to my unit of work
public void BeginTransaction()
{
transaction = session.BeginTransaction(IsolationLevel.ReadCommitted);
}
public void BeginTransaction(IsolationLevel level)
{
transaction = session.BeginTransaction(level);
}
I removed it from the constructor
The ISession itself is a unit-of-work implementation, so implementing your own is entirely optional.
IsolationLevel is declared in the System.Data namespace. ReadCommitted is the most commonly used; this is also the default for SQL Server. I tried using Chaos once just because I like the name but SQL Server doesn't support it.
The code you have in the question creates a transaction as soon as the session is opened, so it's a transaction per UOW implementation; note that if you call Commit or Rollback, any additional operations will occur outside of transaction scope. I really dislike this and prefer to control my own transaction scope, so I would have a BeginTransaction in my UOW.
Classes that implement IDisposable must implement the Dispose method and should be used in a using block or disposed of manually. It means that the class has clean up operations that need to run, such as closing the ISession. Here, the Dispose method should just call ISession.Dispose instead of just closing the session.
I'm running into issues with my ISessions in NHibernate. I keep getting "Session Closed!" errors. Can some one please show me the correct pattern including a definition of the following methods and when to use each:
ISession.Close()
ISession.Dispose()
ISession.Disconnect()
Here's my problem. I have a callback setup to fire off a process that awards badges to players every couple of minutes. However I keep getting "Session Closed!" errors or errors about not being able to associate collections.
Here's my Repository:
public class NHibernateRepository : IRepository
{
#region Fields
private ISession _session;
private readonly ISessionFactory _sessionFactory;
#endregion
#region Constructors
public NHibernateRepository(ISessionFactory sessionFactory)
{
_sessionFactory = sessionFactory;
}
#endregion
#region IRepository Implementation
public ISession OpenSession()
{
_session = _sessionFactory.OpenSession();
return _session;
}
public IQueryable<TModel> All<TModel>()
{
return _session.Linq<TModel>();
}
public void Save<TModel>(TModel model)
{
_session.Save(model);
}
public void Update<TModel>(TModel model)
{
_session.Update(model);
}
public void Delete<TModel>(TModel model)
{
_session.Delete(model);
}
public ITransaction BeginTransaction()
{
return _session.BeginTransaction();
}
public void Flush()
{
_session.Flush();
}
#endregion
}
Here's my usage. The repository is getting injected via Structure Map
private Object _awardBadgesLock = new object(); //In case the callback happens again before the previous one completes
public void AwardBadges()
{
lock (_awardBadgesLock)
{
using(session = _repository.OpenSession())
{
foreach (var user in _repository.All<User>().ToList())
{
var userPuzzles = _repository.All<Puzzle>().ByUser(user.Id).ToList();
var userVotes = _repository.All<Vote>().Where(x => x.UserId == user.Id).ToList();
var userSolutions = _repository.All<Solution>().ByUser(user.Id).ToList().Where(x => !userPuzzles.Select(y => y.Id).Contains(x.PuzzleId));
var ledPuzzles = GetPuzzlesLedByUser(user.Id);
AwardPlayerBadge(user, userSolutions);
AwardCriticBadge(user, userVotes);
AwardCreatorBadge(user, userPuzzles);
AwardRidlerBadge(user, userPuzzles);
AwardSupporterBadge(user, userVotes);
AwardPopularBadge(user, userPuzzles);
AwardNotableBadge(user, userPuzzles);
AwardFamousBadge(user, userPuzzles);
AwardLeaderBadge(user, ledPuzzles);
using (var tx = _repository.BeginTransaction())
{
_repository.Update(user);
tx.Commit();
}
}
}
}
}
You should always use session.Dispose();
The other are for very strange occurances
I advice you to read the documentation of ISession on
https://nhibernate.svn.sourceforge.net/svnroot/nhibernate/trunk/nhibernate/src/NHibernate/ISession.cs
Anyway the proper way to clean up when you are finished with the session is to dispose it (or better, surround the usage with using statement). In this case, "using" closes the session and suppresses the finalizer, i.e. it prevents the session object from unnecessarily surviving the next garbage collecting and saves the memory.
If the connection is already closed, disposing it will not throw an exception. On the other hand, closing after disposing (or after closing) throws an exception.
The documentation recommends calling disconnect instead of closing, because this releases the connection to the connection pool. You should call Reconnect before using a disconnected session.
For my needs, I always use "using" which calls Dispose and have never used the othe two functions.
The issue lies in the fact the ISession is not thread-safe. There were multiple methods being fired on separate threads that all created an instance of ISession. The issue was really with the fact that they all shared the same SessionFactory. Image both of these methods are fired off on separate threads:
ISessionFactory _sessionFactory;
void MethodOne()
{
using(ISession session = _sessionFactory.OpenSession())
{
//Do something with really quick with the session
//Then dispose of it
}
}
void MethodTwo()
{
//OpenSession() actually returns the same instance used in the
//previous method that has already disposed of the object;
using(ISession session = _sessionFactory.OpenSession())
{
//Do something with a session that has already been disposed
//throws errors
}
}
How I fixed it was basically ditching NHIbernate in these scenarios and called stored procs instead. I think it turned out to be more performant in my situation anyway.
About the problem, your method of locking is right as long as you dispose the session but probably the bug lies under another part of your codes. by the way about the design, it is better that you pass the session variable to repositories because of unit of work implementation of the session and aggregate root's transaction like this:
using (ISession session = SessionFactory.OpenSession())
{
Repository1 rep1 = new Repository1(session);
Repository2 rep1 = new Repository2(session);
Repository3 rep1 = new Repository3(session);
// some logics
using (var tx = session.BeginTransaction())
tx.Commit();
}
.
.
.
I'm using Castle ActiveRecord for persistence, and I'm trying to write a base class for my persistence tests which will do the following:
Open a transaction for each test case and roll it back at the end of the test case, so that I get a clean DB for each test case without me having to rebuild the schema for each test case.
Provide the ability to flush my NHibernate session and get a new one in the middle of a test, so that I know that my persistence operations have really hit the DB rather than just the NHibernate session.
In order to prove that my base class (ARTestBase) is working, I've come up with the following sample tests.
[TestFixture]
public class ARTestBaseTest : ARTestBase
{
[Test]
public void object_created_in_this_test_should_not_get_committed_to_db()
{
ActiveRecordMediator<Entity>.Save(new Entity {Name = "test"});
Assert.That(ActiveRecordMediator<Entity>.Count(), Is.EqualTo(1));
}
[Test]
public void object_created_in_previous_test_should_not_have_been_committed_to_db()
{
ActiveRecordMediator<Entity>.Save(new Entity {Name = "test"});
Assert.That(ActiveRecordMediator<Entity>.Count(), Is.EqualTo(1));
}
[Test]
public void calling_flush_should_make_nhibernate_retrieve_fresh_objects()
{
var savedEntity = new Entity {Name = "test"};
ActiveRecordMediator<Entity>.Save(savedEntity);
Flush();
// Could use FindOne, but then this test would fail if the transactions aren't being rolled back
foreach (var entity in ActiveRecordMediator<Entity>.FindAll())
{
Assert.That(entity, Is.Not.SameAs(savedEntity));
}
}
}
Here is my best effort at the base class. It correctly implements Flush(), so the third test case passes. However it does not rollback the transactions, so the second test fails.
public class ARTestBase
{
private SessionScope sessionScope;
private TransactionScope transactionScope;
[TestFixtureSetUp]
public void InitialiseAR()
{
ActiveRecordStarter.ResetInitializationFlag();
ActiveRecordStarter.Initialize(typeof (Entity).Assembly, ActiveRecordSectionHandler.Instance);
ActiveRecordStarter.CreateSchema();
}
[SetUp]
public virtual void SetUp()
{
transactionScope = new TransactionScope(OnDispose.Rollback);
sessionScope = new SessionScope();
}
[TearDown]
public virtual void TearDown()
{
sessionScope.Dispose();
transactionScope.Dispose();
}
protected void Flush()
{
sessionScope.Dispose();
sessionScope = new SessionScope();
}
[TestFixtureTearDown]
public virtual void TestFixtureTearDown()
{
SQLiteProvider.ExplicitlyDestroyConnection();
}
}
Note that I'm using a custom SQLite provider with an in-memory database. My custom provider, taken from this blog post, keeps the connection open at all times to maintain the schema. Removing this and using a regular SQL Server database doesn't change the behaviour.
Is there a way to acheive the required behaviour?
Not too sure about ActiveRecord, but in NHibernate a transaction belongs to a session, not the otherway round.
If you've used ADO.Net a lot, this will make more sense, as to create an IDbTransaction you need to use the connection. ActiveRecord's TransactionScope (and NHibnerate's ITransaction) essentially wrap an IDbTransaction, so you need to create the SessionScope before the TransactionScope.
What you might also find (depending on if you're using NHibernate 1.2 GA or NHibernate 2.*, and what FlushMode your SessionScope has) is that your call to FindAll() may cause the session to flush anyway, as NHibernate will realise that it can't retrieve the correct data without actioning the last call to Save.
This all said and done, have you tried using SessionScope.Flush() instead of creating a new SessionScope?
Using SessionScope.Flush() makes my third test fail. As I understand it, Flush() executes the SQL to push my records into the DB, but does not evict objects from the session. That fits with what you say about FindAll() causing a flush.
What I really want is SessionScope.Flush() (to synchronise state of DB with session) plus SessionScope.EvictAll() (to ensure I get fresh objects in subsequent queries). My new SessionScope() was an attempt to simulate EvictAll().
Your comments about the session enclosing the transaction rather than the other way round did give me an idea. I'm not sure how kosher it is to create a new SessionScope inside a TransactionScope inside a flushed SessionScope, and expect it to participate in the transaction, but it seems to work:
public abstract class ARTestBase
{
private SessionScope sessionScope;
private TransactionScope transactionScope;
private bool reverse;
private IList<SessionScope> undisposedScopes;
[TestFixtureSetUp]
public void InitialiseAR()
{
ActiveRecordStarter.ResetInitializationFlag();
ActiveRecordStarter.Initialize(typeof (Entity).Assembly, ActiveRecordSectionHandler.Instance);
ActiveRecordStarter.CreateSchema();
InitialiseIoC();
undisposedScopes = new List<SessionScope>();
}
[SetUp]
public virtual void SetUp()
{
sessionScope = new SessionScope();
transactionScope = new TransactionScope(OnDispose.Rollback);
transactionScope.VoteRollBack();
base.CreateInstanceUnderTest();
reverse = false;
}
[TearDown]
public virtual void TearDown()
{
if (reverse)
{
sessionScope.Dispose();
transactionScope.Dispose();
}
else
{
transactionScope.Dispose();
sessionScope.Dispose();
}
}
[TestFixtureTearDown]
public virtual void TestFixtureTearDown()
{
foreach (var scope in undisposedScopes)
{
scope.Dispose();
}
SQLiteProvider.ExplicitlyDestroyConnection();
}
protected void Flush()
{
reverse = true;
sessionScope.Flush();
undisposedScopes.Add(sessionScope);
sessionScope = new SessionScope();
}
}
On further thought, this won't allow you to flush more than once in each test case. I think I can handle that by tracking the scopes more carefully. I might look into it later.