NHibernate Session/Transaction Woes - nhibernate

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.

Related

ASP .NET Entity Framework Core Cannot access a disposed object

Wanting to get into .NET Core, I created a WEB API that takes a file upload and then saves the transactions in the file into a DB table. I'm using .NET Core 2 with Entity Framework Core. I created my context using the example from here.
My problem is that I get the error "System.ObjectDisposedException Cannot access a disposed object" when it tries to save to the context object in my repository. It's a simple stack, so I'm hoping someone can help me out.
My container setup looks like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddDbContext<SyncFinContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddScoped<ITransactionProcessor, TransactionProcessor>();
services.AddScoped<ITransactionRepository, TransactionRepository>();
}
My DBInitializer which I also got from the link above:
public static class DbInitializer
{
public static async Task Initialize(SyncFinContext context)
{
await context.Database.EnsureCreatedAsync();
// Look for any students.
if (context.Transactions.Any())
{
return; // DB has been seeded
}
var ts = new Transaction[]
{
// insert test data here
};
await context.SaveChangesAsync();
}
}
My DB Context:
public class SyncFinContext : DbContext
{
public SyncFinContext(DbContextOptions<SyncFinContext> options) : base(options)
{
}
public DbSet<Transaction> Transactions { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Transaction>().ToTable("Transactions");
}
}
My Controller looks like this:
[Produces("application/json")]
public class TransactionController : Controller
{
ITransactionRepository _transactionRepository { get; set; }
ITransactionProcessor _transactionProcessor { get; set; }
public TransactionController(ITransactionRepository m, ITransactionProcessor p) : base()
{
_transactionRepository = m;
_transactionProcessor = p;
}
// POST: transaction/import
[HttpPost]
public async void Import(List<IFormFile> files)
{
if (files == null || files.Count == 0)
{
throw new FileNotFoundException("No file was received.");
}
// copy file to temp location so that it can be processed
var filepath = Path.GetTempFileName();
using (var stream = new FileStream(filepath, FileMode.Create))
{
await files[0].CopyToAsync(stream);
}
ImportTransactionRequest input = new ImportTransactionRequest
{
FilePath = filepath
};
var transactions = await _transactionProcessor.ReadDocument(filepath);
await _transactionRepository.AddBulk(transactions);
}
}
And my repository looks like this:
public class TransactionRepository : ITransactionRepository
{
// TODO: move the context
private SyncFinContext _context;
public TransactionRepository(SyncFinContext context)
{
_context = context;
}
public async Task AddBulk(List<Transaction> transactions)
{
foreach(var t in transactions)
{
await _context.Transactions.AddAsync(t);
}
_context.SaveChanges();
}
}
For full transparency, the transaction Processor just gets a list of rows from a csv:
public async Task<List<Transaction>> ReadDocument(string filepath)
{
try
{
var ret = new List<Transaction>();
var lines = await File.ReadAllLinesAsync(filepath);
foreach (var line in lines)
{
var parts = line.Split(',');
var tx = new Transaction
{
PostedDate = DateTime.Parse(parts[0]),
TransactionDate = DateTime.Parse(parts[1]),
Description = parts[2],
Deposit = ParseDecimal(parts[3]),
Withdrawal = ParseDecimal(parts[4]),
Balance = ParseDecimal(parts[5])
};
ret.Add(tx);
}
return ret;
}
catch(Exception e)
{
throw;
}
}
I've read where the whole stack must be async in order for the db context instance to be available, and, unless I'm doing it wrong, I seem to be doing that, as you can see above.
My expectations are that AddDbContext() will indeed properly scope the context to be available throughout the stack unless I explicitly dispose of it. I have not found anything to make me think otherwise.
I've tried hard-coding data in my DB Initializer also, as I read that may be a factor, but that does not solve the problem. Not sure what else to try. If someone can give me some ideas I would appreciate it.
The Import() action method needs to have a return type of Task. MVC will await execute an action method with a return type of Task.
Also, probably best to get in the habit of returning an IActionResult on your action methods. The task based equivalent is Task<IActionResult>. This makes your controllers easier to test.
Since the AddBulk(List<Transaction> transactions) method is public async Task, the DbContext will be disposed if any part returns void (not awaited) at any point.
Try changing _context.SaveChanges();
To await _context.SaveChangesAsync();
This would ensure a Task is being returned and not void.
https://stackoverflow.com/a/46308661/3062956
https://learn.microsoft.com/en-us/ef/core/saving/async

ASP.NET Web API - How can I keep a session alive until after custom MediaTypeFormatter is complete?

I have an ASP.Net Web API project. I am using NHibernate in this project; Fluent NHibernate to be specific. I am handling NHib session management using a custom ActionFilterAttribute. It looks like this:
public class SessionManagement : ActionFilterAttribute
{
public SessionManagement()
{
SessionFactory = WebApiApplication.SessionFactory;
}
private ISessionFactory SessionFactory { get; set; }
public override void OnActionExecuting(HttpActionContext actionContext)
{
var session = SessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
session.BeginTransaction();
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var session = SessionFactory.GetCurrentSession();
var transaction = session.Transaction;
if (transaction != null && transaction.IsActive)
{
transaction.Commit();
}
session = CurrentSessionContext.Unbind(SessionFactory);
session.Close();
}
This was working well for my needs. However, I have recently added a custom JSON.NET MediaTypeFormatter to format the my action's resulting JSON. The problem I am having is that my ActionFilter OnActionExecuted() method is called before the MediaTypeFormatter's WriteToStreamAsync can do it's job. The result is that lazily loaded (the problem) collections are now not available because the session is closed. What is the best way to handle this? Should I remove the ActionFilter's OnActionExecuted method and just close my session in the MediaTypeFormatter?
Thanks!!
The MediaTypeFormatter is the wrong layer to close the session at because this behavior really has nothing to do with the particular formatter you're using. Here's what I recommend doing:
Derive from HttpContent and create a class that derives from ObjectContent. Override the SerializeToStreamAsync implementation to await the base implementation's SerializeToStreamAsync then close the session:
public class SessionClosingObjectContent : ObjectContent
{
private Session _session;
public SessionClosingObjectContent(Type type, object value, MediaTypeFormatter formatter, Session session)
: base(type, value, formatter)
{
_session = session;
}
protected async override Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
await base.SerializeToStreamAsync(stream, context);
// Close the session and anything else you need to do
_session.Close();
}
}
Now in your action filter, instead of closing the session, you want to replace the response Content with your new class that closes the session:
public class SessionManagement : ActionFilterAttribute
{
public SessionManagement()
{
SessionFactory = WebApiApplication.SessionFactory;
}
private ISessionFactory SessionFactory { get; set; }
public override void OnActionExecuting(HttpActionContext actionContext)
{
var session = SessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
session.BeginTransaction();
}
public override void OnActionExecuted(HttpActionExecutedContext actionExecutedContext)
{
var session = SessionFactory.GetCurrentSession();
var response = actionExecutedContext.Response;
if (response.Content != null)
{
ObjectContent objectContent = response.Content as ObjectContent;
if (objectContent != null)
{
response.Content = new SessionClosingObjectContent(objectContent.ObjectType, objectContent.Value, objectContent.Formatter, session);
foreach (KeyValuePair<string, IEnumerable<string>> header in objectContent.Headers)
{
response.Content.Headers.TryAddWithoutValidation(header.Key, header.Value);
}
}
}
}
}
You could also choose instead to return an HttpResponseMessage with your new Content directly from your controller code wherever you need it.

EntityFramework Update Deletes Record

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.

NHibernate Transaction.Commit automatically closes Session

I have a web application that is using the absolute latest version (3.3) and is using session-by-request session management within a HttpModule, so there are no problems with multiple session conflicts. Unfortunately, I am finding that the session is getting automatically closed immediately after I perform a Transaction.Commit which I only do when I am actually performing a Create, Update or Delete. I am finding this within my NHibernate log.
I know for a fact that I am not doing it, because the only call to the ISession.Close function is being done within my HttpModule.
Yes, of course I can put code in my SessionManager to check for the IsClosed parameter and then use the OpenSession function instead of GetCurrentSession, but should this even be happening? Is there some way that I can prevent this either through my configuration or some attribute that I could set on the Session or Transaction object or is this just one of the new features that I can't find any documentation anywhere on?
Please help.
Brian
I was asked to provide some code, so here is the code for the HttpModule:
public class NhibernateModule : IHttpModule
{
public void Dispose()
{
}
public void Init(HttpApplication context)
{
context.BeginRequest += new EventHandler(context_BeginRequest);
context.EndRequest += new EventHandler(context_EndRequest);
}
public void context_BeginRequest(Object sender, EventArgs e)
{
WebSessionContext.Bind(NhibernateSessionManager.GetContextSession());
}
public void context_EndRequest(Object sender, EventArgs e)
{
ISession session = WebSessionContext.Unbind(NhibernateSessionManager.SessionFactory);
if (session != null)
{
if (session.Transaction != null && session.Transaction.IsActive)
{
session.Transaction.Rollback();
}
else
session.Flush();
session.Close();
}
}
}
}
Next, you will find the original code that I am using within my SessionManager:
public sealed class NhibernateSessionManager
{
private readonly ISessionFactory sessionFactory;
public static ISessionFactory SessionFactory
{
get { return Instance.sessionFactory; }
}
private ISessionFactory GetSessionFactory()
{
return sessionFactory;
}
public static NhibernateSessionManager Instance
{
get { return NestedSessionManager.sessionManager; }
}
public static ISession GetContextSession()
{
ISession session;
if (CurrentSessionContext.HasBind(SessionFactory))
{
session = SessionFactory.GetCurrentSession();
}
else
{
session = SessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
}
return session;
}
private NhibernateSessionManager()
{
if (sessionFactory == null)
{
Configuration configuration;
configuration = new Configuration().Configure(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "web.config"));
log4net.Config.XmlConfigurator.ConfigureAndWatch(new FileInfo(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "web.config")));
//Configuration configuration = new Configuration().Configure();
if (configuration == null)
{
throw new InvalidOperationException("NHibernate configuration is null.");
}
else
{
sessionFactory = configuration.BuildSessionFactory();
if (sessionFactory == null)
throw new InvalidOperationException("Call to BuildSessionFactory() returned null.");
}
}
}
class NestedSessionManager
{
internal static readonly NhibernateSessionManager sessionManager = new NhibernateSessionManager();
}
}
Last, here is a function that is currently causing the session to close immediately after the Transaction.Commit(). Each of the inner functions retrieve the current session and then process a Save call.
public static Int32 AddVideo(VideoFile Video, Int32 UserID, Int16 InstID)
{
log.Debug("Begin AddVideo");
Int32 FileID = 0;
using (ISession Session = NhibernateSessionManager.GetContextSession())
{
using (ITransaction Transaction = Session.BeginTransaction())
{
Video.Created = DateTime.Now;
Video.Modified = DateTime.Now;
FileID = (Int32)Session.Save(Video);
Video.FileID = FileID;
// Need to process through all the categories and insert records into the ivxFileCategories table
// to associate the newly created file with the chosen categories
if (Video.CategoryAssociations != null)
{
log.Info("Number of categories to be associated with the video: " + Video.CategoryAssociations.Count);
for (int i = 0; i < Video.CategoryAssociations.Count; i++)
{
CategoryFileAssociation Assoc = (CategoryFileAssociation)Video.CategoryAssociations[i];
Assoc.FileID = FileID;
AssociationManager.AddCategoryFileTransaction(Assoc);
}
}
// Need to add the default file access for the UserDetail that is creating the new video which will always
// be Admin because the UserDetail creating a file should always have admin access over the file, no matter
// what their default role is.
AssociationManager.AddFileAccessTransaction(FileID, UserID, UserClassConstants.IVXUSERCLASS_ADMIN);
// Need to add the institutional association based on whether the new video was created by a librarian
// or one of the iVidix admins
AssociationManager.AddInstitutionFileTransaction(InstID, FileID);
Transaction.Commit();
}
}
log.Debug("End AddVideo");
return FileID;
}
The session will by disposed in the AddVideo method because you are using the using Statement for the session.
using (ISession Session = NhibernateSessionManager.GetContextSession())
{
}
I would totally recommend striping away the transaction stuff
using (ISession Session = NhibernateSessionManager.GetContextSession())
{
using (ITransaction Transaction = Session.BeginTransaction())
{
...
}
}
and move it into the begin/end request. This way you have a UOW for each request. The reason you session is closing IS because of the using statement.
Your begin request code can then be something along the lines of:-
var session = sessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
session.BeginTransaction();
and your end request:-
var session = CurrentSessionContext.Unbind(sessionFactory);
if (session != null)
{
if (session.Transaction.IsActive)
{
try
{
session.Transaction.Commit();
}
catch
{
session.Transaction.Rollback();
}
}
session.Close();
}
I have this in my global.asax
public static ISessionFactory SessionFactory { get; set; }
and this in my repositories
public ISession Session
{
get
{
return SessionFactory.GetCurrentSession();
}
}
Now I use IOC to pass my sessionFactory to my repository layers, else you will need to pass this in yourself manually.
Committing transaction will end that session.
Move your transaction start to context_BeginRequest and the commit/cleanup in context_EndRequest
I actually don't like session in view pattern and prefer to keep my transactions open as short as possible and rather inject session into the controller. I then perform the transaction in the action or service. I prefer this fine grained control of the transactions and keeps them short lived avoiding any locking issues.

How to have manage sub Transactions or nested transactions?

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();
}
}
}