I want to have something like sub-transactions, in that you can mark a point where you would start the sub-transaction, then at the point of descision for that bit, you can either roll-back (abort the sub-bit) or carry on, effectively commiting, when the out transation commits. Of course, if you abort the outer transaction, the marked bit aborts too.
How can I do that with NHibernate but the transaction is being closed during the fisrt commit and thus i`m having the error message
no open transaction to commit
My code is as follows:
API.Data.Session session = API.Data.SessionManager.GetSession();
session.BeginTransaction();
try
{
Project project = Project.Load(ID);
...........
Save(project);
.....................
session.CommitTransaction();
}
catch
{
session.RollbackTransaction();
throw;
}
public void save(Project project)
{
Data.SessionManager.GetSession().BeginTransaction();
try
{
Save();
LogIssueChange(test);
Data.SessionManager.GetSession().CommitTransaction();
}
catch
{
Data.SessionManager.GetSession().RollbackTransaction();
throw;
}
}
The native support is provided via Nested Transactions and the TransactionScope class. Note that AFAIK this is supported by Sql Server and Oracle although it may work for other databases if they support distributed transactions as well as the API that plugs to System.Data supports it as well.
see these SO questions https://stackoverflow.com/questions/tagged/nhibernate+transactionscope
You can't, NHibernate does not support nested transactions. You might be able to achieve this with the Systems.Transaction namespace.
Edit: DeferableTransaction implementation is below. I have this as an extension method on ISession. That said, usage has been very rare.
Extension methods:
public static DeferableTransaction BeginOrDeferTransaction(this ISession session)
{
return new DeferableTransaction(session);
}
public static DeferableTransaction BeginOrDeferTransaction(this ISession session, IsolationLevel isolationLevel)
{
return new DeferableTransaction(session, isolationLevel);
}
Implementation:
/// <summary>
/// Begins a transaction or defers to an existing transaction. The IsolationLevel will not be changed
/// on an existing transaction.
/// </summary>
public class DeferableTransaction : IDisposable
{
private readonly bool _ownedTransaction;
private readonly ITransaction _transaction;
public DeferableTransaction(ISession session)
: this(session, IsolationLevel.ReadCommitted)
{}
public DeferableTransaction(ISession session, IsolationLevel isolationLevel)
{
if (session.Transaction.IsActive)
{
_ownedTransaction = false;
_transaction = session.Transaction;
}
else
{
_ownedTransaction = true;
_transaction = session.BeginTransaction(isolationLevel);
}
}
public bool OwnedTransaction
{
get { return _ownedTransaction; }
}
public void CommitOrDefer()
{
if (_ownedTransaction)
{
_transaction.Commit();
}
}
public void RollbackOrDefer()
{
if (_ownedTransaction)
{
_transaction.Rollback();
}
}
public void Enlist(IDbCommand command)
{
_transaction.Enlist(command);
}
public bool IsActive
{
get { return _transaction.IsActive; }
}
public bool WasCommitted
{
get { return _transaction.WasCommitted; }
}
public bool WasRolledBack
{
get { return _transaction.WasRolledBack; }
}
public void Dispose()
{
if (_ownedTransaction)
{
_transaction.Dispose();
}
}
}
Related
I use Entity Framework on my project and I decided to change standard way, which means use - 'using'.
I write my DB layer for communicate Database, but sometimes return this error, please help me to fix this problem:
An exception of type 'System.ObjectDisposedException' occurred in
EntityFramework.SqlServer.dll but was not handled in user code
Additional information: The ObjectContext instance has been disposed
and can no longer be used for operations that require a connection.
Here is my BaseController Code:
//For Data Accsess
public CBEntities DB
{
get
{
return DataAccsesLayer.DbeEntities;
}
}
protected override void Dispose(bool disposing)
{
if (disposing)
{
if (DataAccsesLayer.DbeEntities != null)
{
DataAccsesLayer.DbeEntities.Dispose();
DataAccsesLayer.DbeEntities = null;
}
}
base.Dispose(disposing);
}
Data project:
//this part of code is member of Data project which is Data Layer
public static class DataAccsesLayer
{
private static CBEntities _context;
public static CBEntities DbeEntities
{
get
{
if (_context == null)
{
_context = new CBEntities();
return _context;
}
else
{
return _context;
}
}
set { _context = value; }
}
}
I'm developing a MVC4 app using .Net4.5, EF5 and MSSQL 2008R2. I'm using Db Context and autogenerated entity classes/models and the Unit of Work pattern.
When I attempt to update a table record through the Edit ActionResult method, the record gets deleted when I data updated was originally null, like adding a middle name for example.
I can follow the object from the ActionResult to the GenericRepository to the UnitOfWork Save() where the db context savechanges() is called. The data is there until then. After that, I'm not sure how to debug it.
So, any help with either debugging or with resolving this would be appreciated. I can successfully create and delete table records. I also tried simply using the db context in the Edit ActionResult with the same delete results. I tested the mapped stored procedure, which updated correctly.
[HttpPost]
public ActionResult Edit(v_Demographics vm)
{
try
{
if (ModelState.IsValid)
{
unitOfWork.DemographicsRepository.Update(vm);
unitOfWork.Save();
//db.Entry(vm).State = EntityState.Modified;
//db.SaveChanges();
return RedirectToAction("Index");
}
}
catch (DataException)
{
//Log the error (add a variable name after DataException)
ModelState.AddModelError("", "Unable to save changes. Try again, and if the problem persists see your system administrator.");
}
return View(vm);
}
public class GenericRepository<TEntity> where TEntity : class
{
internal SEntities db = new SEntities();
internal DbSet<TEntity> dbSet;
public GenericRepository(AIPIMSEntities db)
{
this.db = db;
this.dbSet = db.Set<TEntity>();
}
public virtual void Update(TEntity entityToUpdate)
{
dbSet.Attach(entityToUpdate);
db.Entry(entityToUpdate).State = EntityState.Modified;
}
}
public class UnitOfWork : IDisposable
{
private AIPIMSEntities db = new AIPIMSEntities();
private GenericRepository<v_Demographics> demographicsRepository;
public GenericRepository<v_Demographics> DemographicsRepository
{
get
{
if (this.demographicsRepository == null)
{
this.demographicsRepository = new GenericRepository<v_Demographics>(db);
}
return demographicsRepository;
}
}
public void Save()
{
db.SaveChanges();
}
private bool disposed = false;
protected virtual void Dispose(bool disposing)
{
if (!this.disposed)
{
if (disposing)
{
db.Dispose();
}
}
this.disposed = true;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
}
As it turns out, the delete error was caused by a MSSQL view. The code here is based upon this tutorial, and works very now.
I'm trying to use a session-per-request pattern and I'm having trouble with getting a record right after it's been saved. The reason for doing that being that I need to get the records that the foreign keys relate to.
Some (simplified) code:
// UnitOfWork Attribute
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
SessionFactory.Begin();
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
if (filterContext.Exception == null) {
try {
SessionFactory.Commit();
} catch {
SessionFactory.Rollback();
throw;
}
} else {
SessionFactory.Rollback();
throw filterContext.Exception;
}
}
// Service layer
public void Save(Form form)
{
_repository.Save(form);
var savedForm = _repository.Get(form.Id);
SendEmail(savedForm);
}
// Repository
public void Save(Form form)
{
var session = SessionFactory.CurrentSession;
session.SaveOrUpdate(form);
}
The problem is that when I try to get the record, the transaction hasn't yet been committed, so it just gives me what's already in the session. Am I just going to have to commit the transaction after saving, and open a new transaction for getting it?
Thanks
Update:
I've implemented the Agathas Storefront way of doing things, giving the service layer control over the transactions, i.e.:
public class UnitOfWork : IUnitOfWork
{
public void Commit()
{
var session = SessionFactory.CurrentSession;
using (ITransaction transaction = session.BeginTransaction())
{
try {
transaction.Commit();
} catch {
transaction.Rollback();
throw;
}
}
}
public void Clear()
{
var session = SessionFactory.CurrentSession;
session.Clear();
}
}
Then in the service layer:
public void SaveForm(Form form)
{
_repository.Save(form);
_uow.Commit();
_uow.Clear();
var savedForm = _repository.Get(form.Id);
SendEmail(savedForm);
}
Update 2
OK, I think I've found a suitable solution. I've gone back to the transaction-per-request pattern, and after saving the form I'm now flushing it and then evicting the form from the session in order to force NH to get it from the DB.
// Service layer
public void SaveForm(Form form)
{
_repository.Save(form);
var savedForm = _repository.Get(form.Id);
SendEmail(savedForm);
}
// Repository
public void Save(Form form)
{
var session = SessionFactory.CurrentSession;
session.SaveOrUpdate(form);
session.Flush();
session.Evict(form);
}
Until you flush the session or reduce your transaction scope you won't have an ID because nhibernate will not have inserted the record.
One option, change your id strategy from an identity generated in the database to one that is managed by NHibernate (see options at http://nhibernate.info/doc/nh/en/index.html#mapping-declaration-id-generator) or another option change your Session's flushmode to FlushMode.Auto.
I'm creating an Asp.Net MVC 3 application with NHibernate as my ORM. In my my edit action method I call the Save method in my DatabaseAccessObject class, but instead of updating the object it creates a new one. I can't figure out why.
Here's the code for the method that returns my configured SessionFactory, and my global.asax.cs file where I'm storing the SessionFactory:
public static ISessionFactory CreateSessionFactory()
{
return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString("Server=(local);Database=WebApplicationPbiBoard;Trusted_Connection=True;"))
.Mappings(m => m.FluentMappings.AddFromAssemblyOf<WebApplicationPbiBoard.Models.ScrumModels_Mappings.PbiMap>())
.ExposeConfiguration(cfg => new SchemaUpdate(cfg).Execute(false, true))
.CurrentSessionContext("web")
.BuildSessionFactory();
}
public class MvcApplication : System.Web.HttpApplication
{
public static ISessionFactory SessionFactory { get; private set; }
protected void Application_Start()
{
//my additions
SessionFactory = NHibernateConfigurator.CreateSessionFactory();
}
protected void Application_OnEnd()
{
SessionFactory.Dispose();
}
protected void Application_BeginRequest()
{
ISession session = SessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
}
protected void Application_EndRequest()
{
CurrentSessionContext.Unbind(SessionFactory);
}
}
Here's the relevant snippet from my DataAccessObject, which simply wraps NHibernate CRUD operations:
public class DatabaseAccessObject<T> where T : class
{
private readonly ISession session = MvcApplication.SessionFactory.GetCurrentSession();
private ISession Session { get { return session; } }
public T Save(T obj)
{
ITransaction transaction = null;
try
{
transaction = Session.BeginTransaction();
Session.SaveOrUpdate(obj);
transaction.Commit();
return obj;
}
catch (Exception ex)
{
if (transaction != null && transaction.IsActive)
transaction.Rollback();
throw;
}
}
And finally, here's the code for my Http-Post edit method:
private readonly DatabaseAccessObject<Sprint> db = new DatabaseAccessObject<Sprint>();
private DatabaseAccessObject<Sprint> Db { get { return db; } }
[HttpPost]
public ActionResult Edit(Sprint editedSprint)
{
if (ModelState.IsValid)
{
Db.Save(editedSprint);
return RedirectToAction("Index");
}
else
return View(editedSprint);
}
Any help would be appreciated.
The object you're saving probably hasn't got the Id set on it.
SaveOrUpdate does one of two things:
- Update() if the Id is set.
- Save() if the Id is not set.
refer to the docs:
http://www.nhforge.org/doc/nh/en/index.html
Andreas Ohlund has an excellent article here on how to use Structuremap to wire the NHibernate session so that it enlists in the NSB transaction automatically.
Does anyone know if it is possible to achieve the same with Autofac?
I have been given the awnser by a colleague
public class NHibernateMessageModule : IMessageModule
{
/// <summary>
/// Injected SessionManager.
/// </summary>
public ISessionManager SessionManager { get; set; }
public void HandleBeginMessage()
{
//this session need for NServiceBus and for us
ThreadStaticSessionContext.Bind(SessionManager.OpenSession()); //CurrentSessionContext or ThreadStaticSessionContext
}
public void HandleEndMessage()
{
SessionManager.Session.Flush();
}
public void HandleError()
{
}
}
public interface ISessionManager
{
ISession Session { get; }
ISession OpenSession();
bool IsSessionOpened { get; }
void CloseSession();
}
public class NHibernateSessionManager : ISessionManager
{
private ISessionFactory _sessionFactory;
private ISession _session;
public ISession Session
{
get { return _session; }
private set { _session = value; }
}
public SchemaExport SchemaExport { get; set; }
public NHibernateSessionManager(ISessionFactory sessionFactory)
{
_sessionFactory = sessionFactory;
}
public bool IsSessionOpened
{
get { return Session != null && Session.IsOpen; }
}
public ISession OpenSession()
{
if(Session == null)
{
Session = _sessionFactory.OpenSession();
if (SchemaExport != null)
SchemaExport.Execute(true, true, false, Session.Connection, null);
}
return Session;
}
public void CloseSession()
{
if (Session != null && Session.IsOpen)
{
Session.Flush();
Session.Close();
}
Session = null;
}
}
You do exactly the same as in the articke you mention but select one of the Autofac lifescopes. If you have other classes involved in message handling where you want your session to be injected, you use InstancePerLifetimeScope like this
public class EndpointConfig : IConfigureThisEndpoint, AsA_Publisher, IWantCustomInitialization
{
public void Init()
{
var builder = new ContainerBuilder();
builder.Register(s => SessionFactory.CreateSessionFactory()).As<ISessionFactory>().SingleInstance();
builder.Register(x => x.Resolve<ISessionFactory>().OpenSession()).As<ISession>().InstancePerLifetimeScope();
var container = builder.Build();
Configure.With().AutofacBuilder(container);
}
}
You can also register any other dependencies you need within your NSB context and you will be sure it is instantiated and dosposed properly due to the use of child container.