NHibernate transaction: Why does this result in committed data? - nhibernate

The following code demonstrates a misleading situation in which data
is committed to the database, even though commit is never called on a
transaction.
Could anyone explain why?
[TestFixture]
public class TestFixture
{
[Test]
public void Test()
{
var config = DoConfiguration();
using(var factory = config.BuildSessionFactory())
{
using (var session = factory.OpenSession())
{
CallSessionContext.Bind(session);
using(new TransactionScope())
{
using (session.BeginTransaction())
{
var myEntity = session
.CreateQuery("from myEntity")
.List<MyEntity>()[0];
myEntity.Name = "test name";
}
var myEntity2 = session
.CreateQuery("from myEntity")
.List<MyEntity>()[0];
myEntity2.Name = "test name";
session.Flush();
}
CallSessionContext.Unbind(factory);
}
}
}
}

Explicitly calling session.flush() is persisting your changes. Discussed in detail in this post

Related

NHibernate.LazyInitializationException .Net Web API

I am trying to use NHibernate in my project, i am getting the following error
"Initializing[BO.Job#34543]-failed to lazily initialize a collection of role: BO.Job.bInterview, no session or session was closed". can someone help me.
public HttpResponseMessage GetbyId(int Id)
{
Job job = new Job();
try
{
using (ISession session = NHibernateSession.OpenSession()) // Open a session to conect to the database
{
// books = session.Query<Book>().ToList(); // Querying to get all the jobs
JobRepo = new Repo<Job>(session);
job = JobRepo.GetById(Id, "Job_selectbyId");
}
return Request.CreateResponse(HttpStatusCode.OK, job);
}
catch (Exception exp)
{
Log.Error(exp);
return Request.CreateResponse(HttpStatusCode.ExpectationFailed, job);
}
}
public T GetById(int id,string SPName)
{
T result;
//return await Task.Run(() =>
//{
IQuery query = _session.GetNamedQuery(SPName);
query.SetParameter("job_id", id);
//book = _session.Query<Book>().Where(b => b.Id == id).FirstOrDefault();
result = query.UniqueResult<T>();
return result;
//});
}
This is probably because you're trying to access a property that's been marked as 'lazy' after the session has been closed.
My guess is that you're returning job and then the session is being disposed. Some other code is then attempting to access the Interview property of the returned job, which needs to session to implement the lazy loading feature but the session's been disposed.
You can change you query so that Interview is 'pre-fetched' or you can change your mapping so that there's no lazy initialisation on the class or the property.

NHibernate won't let me insert a model in a session if it was part of a failed transaction on that session

Why can't I just insert the model after I get an error back from the database when trying to insert it the first time:
Report report = null;
using (var session = SessionFactory.OpenSession()) {
try {
using (var transaction = session.BeginTransaction()) {
report = new Report();
session.SaveOrUpdate(report);//Exception: Name field required
transaction.Commit();
}
}
catch { }
try {
using (var transaction = session.BeginTransaction()) {
report.Name = "theName";
session.SaveOrUpdate(report);
//Causes Exception:
//Row was updated or deleted by another transaction (or unsaved-value
//mapping was incorrect): [ReportViewer.DataAccess.Models.Report#22]
transaction.Commit();
}
}
catch { }
}
But when I am updating an existing model, and I get an error, I can make my fixes (in this case set a Name) and just try to update again:
Report report = null;
using (var session = SessionFactory.OpenSession()) {
using (var transaction = session.BeginTransaction()) {
report = new Report();
report.Name = "theName";
session.SaveOrUpdate(report);
transaction.Commit();
}
}
using (var session = SessionFactory.OpenSession()) {
//get entity saved from previous session
report = session.Get<Report>(report.Id);
try {
using (var transaction = session.BeginTransaction()) {
report.Name = null;
session.SaveOrUpdate(report);//Exception: Name field required
transaction.Commit();
}
}
catch { }
try {
using (var transaction = session.BeginTransaction()) {
//updates and does not give an error
report.Name = "theName";
session.SaveOrUpdate(report);
transaction.Commit();
}
}
catch { }
}
When an exception triggered by the database occurs, the NHibernate session must be closed (disposed). It is not guaranteed to be consistent (internally or with the DB state) after an exception.
See the chapter on exception handling in the NHibernate reference.
As Oskar said, you should discard an NHibernate session after an exception occurs. However, the reason the insert fails is that you have already made the report persistent by calling SaveOrUpdate on it (you should use Save here). When you call SaveOrUpdate again on the same instance, NHibernate throws an exception because the object is already persistent. Rewriting the code as follows will probably allow the insert to succeed (but it's not recommended):
try {
using (var transaction = session.BeginTransaction()) {
report.Name = "theName";
transaction.Commit();
}
}
In the update example, calling SaveOrUpdate has no effect because the object became persistent when NHibernate loaded it. Understanding NHibernate's instance states and how to work with persistent objects is fundamental and widely misunderstood.
A far better approach is to validate your objects before saving them to the database.

Access Current NHibernate Session in Castle Windsor IOC Container

I am trying to access my current nhibernate session using IOC from within a running Quartz.net Job and every time it comes back as null stating the following:
'NHibernateSession.Current' threw an exception of type 'SharpArch.Domain.PreconditionException' NHibernate.ISession SharpArch.Domain.PreconditionException}. An ISessionStorage has not been configured
Here is my current code setup. I cannot figure out for the life of me how to setup my IOC so that the NHibernate ISession within my IScheduledMessageQueries query is set correctly. The Quartz triggers are working correctly, I just cannot access the ISession to call my queries from within the Job. Any help and/or advice?
Global.cs Code:
protected virtual void InitializeServiceLocator()
{
_container = new WindsorContainer(new XmlInterpreter()).Install(new WebWindsorInstaller());
StartQuartzScheduler();
ComponentRegistrar.AddComponentsTo(_container);
ServiceLocator.SetLocatorProvider(() => new WindsorServiceLocator(_container));
DependencyResolver.SetResolver(new WindsorDependencyResolver(_container));
var activator = new WebApiControllerFactory(_container);
GlobalConfiguration.Configuration.Services.Replace(typeof(IHttpControllerActivator), activator);
}
private static void StartQuartzScheduler()
{
ISchedulerFactory schedulerFactory = new StdSchedulerFactory();
IJobFactory jobFactory = new WindsorJobFactory(_container);
var scheduler = schedulerFactory.GetScheduler();
scheduler.JobFactory = jobFactory;
scheduler.Start();
var sendScheduledMessageJob = new JobDetailImpl("sendScheduledMessageJob", typeof(SendScheduledMessageJob));
var trigger = new CalendarIntervalTriggerImpl
{
StartTimeUtc = DateTime.UtcNow.Subtract(new TimeSpan(1)),
Name = "Daily Trigger",
RepeatIntervalUnit = IntervalUnit.Second,
RepeatInterval = 1
};
scheduler.ScheduleJob(sendScheduledMessageJob, trigger);
}
public class SendScheduledMessageJob : IJob
{
private readonly IScheduledMessageQueries _scheduledMessageQueries;
public SendScheduledMessageJob(IScheduledMessageQueries scheduledMessageQueries)
{
_scheduledMessageQueries = scheduledMessageQueries;
}
public void Execute(IJobExecutionContext context)
{
var unsentScheduledMessages =
_scheduledMessageQueries.GetAllUnsentScheduledMessages(DateTime.Now);
}
}

NHibernate 3.2: SchemaExport not working with SQLite

I'm using an in-memory db for some quick unit tests, using the following code:
public class MemoryDb
{
private static Configuration configuration;
private static ISessionFactory sessionFactory;
static MemoryDb()
{
configuration = new NHibernate.Cfg.Configuration();
configuration.DataBaseIntegration(x =>
{
x.Driver<SQLite20Driver>();
x.Dialect<SQLiteDialect>();
x.ConnectionProvider<DriverConnectionProvider>();
x.KeywordsAutoImport = Hbm2DDLKeyWords.AutoQuote;
x.IsolationLevel = IsolationLevel.ReadCommitted;
x.ConnectionString = "Data Source=:memory:;";
x.Timeout = 255;
x.BatchSize = 100;
x.LogFormattedSql = true;
x.LogSqlInConsole = true;
x.AutoCommentSql = false;
});
configuration.AddMapping(DbHelper.GetAutoMappings());
sessionFactory = configuration.BuildSessionFactory();
}
public static ISession GetSession()
{
var session = sessionFactory.OpenSession();
new SchemaExport(configuration).Execute(true, true, false, session.Connection, null);
return session;
}
}
The problem is that the schema export doesn't seem to be working. On of my tests looks like this:
[Fact]
public void ShouldFindDuplicateByEmail()
{
using (var session = MemoryDb.GetSession())
{
var repo = new NHibernateCustomerRepository(session);
var customer = new Customer();
customer.EmailAddress = "test#test.com";
repo.Save(customer);
var duplicates = repo.FindDuplicates(customer);
Assert.Equal(1, duplicates.Length);
}
}
The test fails with the error no such table: Customers. This all worked with Fluent NHibernate and NHibernate 3.1. I know it's not an issue with the mappings themselves, because the actual application works when I run it against an existing SQL Server db. It only fails when running the tests. Any thoughts?
Edit: If I change only the connection string such that it writes to a file (i.e. x.ConnectionString = "data source=" + Path.GetTempFileName();, the whole thing works. I'm guessing either the schema isn't run correctly against the in-memory db, or it's getting a new in-memory db each time I execute a session command, but have no clue how to figure this out.
I found the answer here: https://forum.hibernate.org/viewtopic.php?p=2397541#p2397541
I had to add the following to the db configuration:
x.ConnectionReleaseMode = ConnectionReleaseMode.OnClose;
Otherwise, NHibernate releases the connection after each statement is flushed, thereby getting rid of the in-memory database with the schema.

NHibernate, TransactionScope and locking

I am trying to use TransactionScope with NHibernate in order to call several methods in one transactions. Data repository methods are like this:
public virtual void Save(T dataObject)
{
try
{
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.RepeatableRead }))
{
this.session.SaveOrUpdate(dataObject);
scope.Complete();
}
}
catch (Exception ex)
{
bool rethrow = ExceptionPolicy.HandleException(ex, "Data Layer Policy");
if (rethrow)
{
throw;
}
}
}
public T GetByNumber(string documentNumber)
{
T document = null;
try
{
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.RepeatableRead }))
{
document = this.Session.CreateCriteria(typeof(T))
.Add(Restrictions.Eq("Number", documentNumber))
.UniqueResult();
scope.Complete();
}
}
catch (Exception ex)
{
bool rethrow = ExceptionPolicy.HandleException(ex, "Data Layer Policy");
if (rethrow)
{
throw;
}
}
return document;
}
I wanted to test row/table locking in transactions so I made several unit tests and some console applications. Here is code from these console applications:
Application which does update:
const string DocumentNumber = "386774321";
Random randomGenerator = new Random();
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions { IsolationLevel = IsolationLevel.RepeatableRead }))
{
using (BillingDocumentRepository billingDocumentRepository = new BillingDocumentRepository())
{
BillingOrderData orderData = billingDocumentRepository.GetByNumber(DocumentNumber);
orderData.Notes = randomGenerator.Next().ToString();
Console.WriteLine(string.Format("SECOND: {0}: Updated notes to {1}.", DateTime.Now.ToString("HH:mm:ss.fffff"), orderData.Notes));
Console.WriteLine(string.Format("SECOND: {0}: Updating order.", DateTime.Now.ToString("HH:mm:ss.fffff")));
Console.WriteLine(string.Format("SECOND: {0}: Going to sleep for 10000ms.", DateTime.Now.ToString("HH:mm:ss.fffff")));
Sleep(10000); // My custom sleep method because I didn't want to use Thread.Sleep for simulating long transaction
billingDocumentRepository.Save(orderData);
}
Console.WriteLine(string.Format("SECOND: {0}: Going to sleep for 10000ms.", DateTime.Now.ToString("HH:mm:ss.fffff")));
Sleep(10000);
Console.WriteLine(string.Format("SECOND: {0}: Completing transaction.", DateTime.Now.ToString("HH:mm:ss.fffff")));
scope.Complete();
}
Application which reads the same row in database:
while (true)
{
using (BillingDocumentRepository repository = new BillingDocumentRepository())
{
Console.WriteLine(string.Format("MAIN: {0}: Getting document.", DateTime.Now.ToString("HH:mm:ss.fffff")));
BillingOrderData billingOrderData = repository.GetByNumber("386774321");
Console.WriteLine(string.Format("MAIN: {0}: Got order with notes {1}.", DateTime.Now.ToString("HH:mm:ss.fffff"), billingOrderData.Notes));
Sleep(1000);
}
}
Problem is that first transaction (which updates row) doesn't lock row for reading at any moment. Second application is reading that row all the time with old value before scope.Complete() and than new value after that. How can I achieve locking with this model?
You should lock when reading. Locking later is "too late":
document = this.Session.CreateCriteria(typeof(T))
.Add(Restrictions.Eq("Number", documentNumber))
.SetLockMode(LockMode.Upgrade)
.SetTimeout(5)
.UniqueResult();
Or:
var doc = session.QueryOver<BillingDocument>()
.Where(c => c.Number== "2233445")
.Lock()
.Upgrade
.UnderlyingCriteria.
SetTimeout(5).
List().
FirstOrNull() as BillingDocument;
There is a session.Lock(object) method.
When you call session.Save(object), NHibernate isn't doing anything in the database until it gets flushed.
Flushing is done (depending on the flush mode, which is usually AutoFlush)
before queries (except Get and Load)
when calling flush explicitly
when committing the transaction (if the connection is created by NH I think)
When the session is flushed, the actual update, insert and delete operations are done on the database and locks are set.
In SQL Server, when the lock is set, the reading transaction is waiting until commit of the updating transaction. When it commits, it reads the committed values (when you are in "Read Committed" isolation).