I have two simple classes which reference each other as a one-to-many relationship defined below:
public class Project
{
public virtual int Id { get; set; }
public virtual string Name { get; set; }
public virtual IList<Document> Documents { get; set; }
}
public class Document
{
public virtual int Id { get; set; }
public string FileName { get; set; }
}
And my mappings are defined as:
public class ProjectMapping : ClassMap<Project>
{
public ProjectMapping()
{
Table("Projects");
Id(x => x.Id).Column("Project_Id").GeneratedBy.TriggerIdentity();
HasMany(x => x.Documents)
.Table("Documents")
.KeyColumn("Document_Project_Id")
.Cascade.AllDeleteOrphan()
.Not.KeyNullable();
Map(x => x.Name).Column("Project_Name");
}
}
public class DocumentMapping : ClassMap<Document>
{
public DocumentMapping()
{
Table("Documents");
Id(x => x.Id).Column("Document_Id").GeneratedBy.TriggerIdentity();
Map(x => x.FileName).Column("Document_File_Name");
}
}
Everything seems to be working fine, adding/updating documents and calling session.Save(project) reflects the correct changes in my database, however if I am to delete a document from a list of documents associated with a project and call session.Save(project) the deleted document never gets deleted from the database.
Any ideas why everything else would work except for delete?
EDIT:
My MVC 4 project is set up with Fluent NHibernate as follows:
public class SessionFactoryHelper
{
public static ISessionFactory CreateSessionFactory()
{
var c = Fluently.Configure();
try
{
//Replace connectionstring and default schema
c.Database(OdbcConfiguration.MyDialect.
ConnectionString(x =>
x.FromConnectionStringWithKey("DBConnect"))
.Driver<NHibernate.Driver.OdbcDriver>()
.Dialect<NHibernate.Dialect.Oracle10gDialect>())
.ExposeConfiguration(cfg => cfg.SetProperty("current_session_context_class", "web"));
c.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Project>());
c.Mappings(m => m.FluentMappings.AddFromAssemblyOf<Document>());
}
catch (Exception ex)
{
Log.WriteLine(ex.ToString());
}
return c.BuildSessionFactory();
}
}
public class MvcApplication : System.Web.HttpApplication
{
public static ISessionFactory SessionFactory { get; private set; }
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
SessionFactory = SessionFactoryHelper.CreateSessionFactory();
}
protected void Application_BeginRequest(object sender, EventArgs e)
{
var session = SessionFactory.OpenSession();
CurrentSessionContext.Bind(session);
}
protected void Application_EndRequest(object sender, EventArgs e)
{
var session = CurrentSessionContext.Unbind(SessionFactory);
session.Dispose();
}
}
My repository is defined as follows:
public class Repository<T> : IRepository<T>
{
public virtual ISession Session
{
get { return MvcApplication.SessionFactory.GetCurrentSession(); }
}
public T FindById(int iId)
{
return Session.Get<T>(iId);
}
public void Save(T obj)
{
using (var transaction = Session.BeginTransaction())
{
try
{
Session.Save(obj);
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
Log.WriteLine(ex.ToString());
}
}
}
public T SaveOrUpdate(T obj)
{
using (var transaction = Session.BeginTransaction())
{
try
{
Session.SaveOrUpdate(obj);
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
Log.WriteLine(ex.ToString());
}
}
return obj;
}
public T Update(T obj)
{
using (var transaction = Session.BeginTransaction())
{
try
{
Session.Update(obj);
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
Log.WriteLine(ex.ToString());
}
}
return obj;
}
}
I have 2 Actions defined in my ProjectsController as follows:
private IRepository<Project> repository;
public ProjectsController()
{
repository = new Repository<Project>();
}
public ActionResult Edit(int iId)
{
Project project = repository.FindById(iId);
if (project == null)
return HttpNotFound();
return View(project);
}
[HttpPost]
public ActionResult Edit(Project project)
{
project = repository.Update(project);
return View(project);
}
If I am to delete a document in my first action (without HttpPost):
project.Documents.RemoveAt(0);
repository.Update(project);
The correct row is removed from the database.
However, if I am to do the very same in the action with HttpPost attribute, the row is never removed.
Also I should note that if I add a document to project.Documents in the action with HttpPost attribute, repository.Update(project) successfully adds the row with the correct foreign key reference to the project. This is only failing when I remove a document.
Have you tried adding .Inverse to your HasMany mapping?
Also, I'm not familiar with the Not.KeyNullable. I don't think it's necessary here.
The cascade setting seems to be correct. The issue mentioned could be elsewhere:
however if I am to delete a document from a list of documents associated with a project
Suspected to me is a session flush mode, or missing explicit call to update parent entity Project, which was previously detached. Assure:
First, that the Flush() was called. In case that project instance is still kept in Session, the default behavior of flushing could be changed. (e.g. session.FlushMode = FlushMode.Never; or Commit without having transaction...)
// 1) checking the explicit Flush()
project.Documents.Remove(doc);
Session.Flush(); // this will delete that orphan
The second could be evicted project instance, needing the explicit update call
// 2) updating evicted project instance
project.Documents.Remove(doc);
Session.Update(project);
//Session.Flush(); // Session session.FlushMode = FlushMode.Auto
The setting inverse will in this case (only) help to reduce one trip to database with UPDATE statement, resetting reference to doc.Project = null, then executing DELETE.
Related
I have a tree structured model and designed it with composite Pattern. for iterating through the entire hierachy Im using Composite Iteration.
I have used this tutorial:
http://www.blackwasp.co.uk/Composite.aspx
but when I want to AutoMap the model, I encounter this problem:
{"The entity '<GetEnumerator>d__0' doesn't have an Id mapped. Use the
Id method to map your identity property. For example: Id(x => x.Id)."}
but getEnumerator is a method. I don't know why handle this like an Entity!!
public IEnumerator<MenuComponent> GetEnumerator()
{
foreach (MenuComponent child in menuComponents)
yield return this;
}
here is my AutoMapping Configuration :
public class AutomappingConfiguration: DefaultAutomappingConfiguration
{
//As we do not explicitly map entities or value objects, we define conventions or exceptions
//for the AutoMapper. We do this by implementing a configuration class.
//this method instructs the AutoMapper to consider only those classes for mapping
//which reside in the same namespace as the Employeeentity.
public override bool ShouldMap(Type type)
{
return type.Namespace == typeof(Menu).Namespace;
}
}
Uploaded the sample code:
public abstract class CombatElement
{
public virtual string Name { get; set; }
public virtual Guid Id { get; set; }
public virtual void Add(
CombatElement element)
{
throw new NotImplementedException();
}
public virtual void
Remove(CombatElement element)
{
throw new NotImplementedException();
}
public virtual
IEnumerable<CombatElement>
GetElements()
{
throw new NotImplementedException();
}
public abstract void Fight();
public abstract void Move();
}
//////
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Diagnostics;
namespace FluentNHibernateMvc3.Models
{
public class Formation : CombatElement
{
private List<CombatElement> _elements;
public virtual IEnumerable<CombatElement> Elements { get { return _elements; } }
public Formation()
{
_elements = new List<CombatElement>();
}
public override void Add(
CombatElement element)
{
_elements.Add(element);
}
public override void
Remove(CombatElement element)
{
_elements.Remove(element);
}
public override void Fight()
{
Debug.WriteLine(this.Name + " Formation is fighting");
}
public override void Move()
{
Debug.WriteLine(this.Name + " Formation is moving");
}
public override
IEnumerable<CombatElement>
GetElements()
{
// yield up this current object first
yield return this;
// iterate through all child elements
foreach (CombatElement fe in
_elements)
{
// + iterate through each of its elements
foreach (CombatElement feInner
in fe.GetElements())
yield return feInner;
}
}
}
}
/////////
public class Soldier : CombatElement
{
public virtual int Rank { get; set; }
public override void Fight()
{
Debug.WriteLine(this.Name + " soldier is fighting");
}
public override void Move()
{
Debug.WriteLine(this.Name + " soldier is fighting");
}
public override
IEnumerable<CombatElement>
GetElements()
{
yield return this;
}
}
and here how I create session factory
// Returns our session factory
private static ISessionFactory CreateSessionFactory()
{
//m => m.FluentMappings.AddFromAssemblyOf<FormationMap>()
return Fluently.Configure()
.Database( CreateDbConfig )
.Mappings(m => m.AutoMappings.Add(CreateMappings()))
.ExposeConfiguration( UpdateSchema )
.CurrentSessionContext<WebSessionContext>()
.BuildSessionFactory();
}
// Returns our database configuration
private static MsSqlConfiguration CreateDbConfig()
{
return MsSqlConfiguration
.MsSql2008
.ConnectionString( c => c.FromConnectionStringWithKey( "testConn" ) );
}
// Returns our mappings
private static AutoPersistenceModel CreateMappings()
{
var cfg = new AutomappingConfiguration();
return AutoMap
.Assemblies(cfg,System.Reflection.Assembly.GetCallingAssembly()).IncludeBase<CombatElement>()
.Conventions.Setup( c => c.Add( DefaultCascade.SaveUpdate() ) );
}
// Updates the database schema if there are any changes to the model,
// or drops and creates it if it doesn't exist
private static void UpdateSchema( Configuration cfg )
{
new SchemaUpdate( cfg )
.Execute( false, true );
}
Does anyone has any idea?
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 use NHiberante at my win service. Sometimes I get
System.ObjectDisposedException: Session is closed!
Object name: 'ISession'.
at NHibernate.Impl.AbstractSessionImpl.ErrorIfClosed()
at NHibernate.Impl.AbstractSessionImpl.CheckAndUpdateSessionStatus()
at NHibernate.Impl.SessionImpl.FireSave(SaveOrUpdateEvent event)
at NHibernate.Impl.SessionImpl.Save(Object obj)
at Attraction.DAL.Repositories.Repository`1.Save(T entity)
at Attraction.VideoDispatcher.Program.ThreadPoolCallback(Object threadContext)
I have no idea what's wrong.
My session management subsystem:
Repository:
public class Repository<T> : IRepository<T>, IDisposable
{
protected readonly bool CommitAtDispose;
public Repository(bool commitAtDispose)
{
CommitAtDispose = commitAtDispose;
StartSession();
}
private void StartSession()
{
if (NHibernateSession == null)
NHibernateHelper.StartSession();
}
public void Dispose()
{
if (CommitAtDispose)
Flush();
}
public void Flush()
{
NHibernateHelper.EndSession();
}
protected override sealed ISession NHibernateSession
{
get
{
return SessionManager.CurrentSession;
}
}
public virtual T GetById(int id)
public virtual List<T> GetAll()
public virtual List<T> GetByPage(int pageIndex, int pageSize)
public virtual int GetCount()
public virtual List<T> GetByCriteria(params ICriterion[] criterion)
public virtual T Save(T entity)
public virtual T Update(T entity)
public virtual void Delete(T entity)
}
}
SessionManager - singletone for provide access to sessionfactory
public class SessionManager : ISessionFactoryProvider
{
private static readonly ILog Log = LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);
private readonly ISessionFactory sessionFactory;
public static ISessionFactory SessionFactory
{
get { return Instance.sessionFactory; }
}
public ISessionFactory GetSessionFactory()
{
return sessionFactory;
}
public static ISession OpenSession()
{
return Instance.GetSessionFactory().OpenSession();
}
public static ISession CurrentSession
{
get
{
if (!CurrentSessionContext.HasBind(Instance.GetSessionFactory()))
return null;
return Instance.GetSessionFactory().GetCurrentSession();
}
}
public static SessionManager Instance
{
get
{
return NestedSessionManager.sessionManager;
}
}
private SessionManager()
{
Log.Info("Start creating factory");
Configuration configuration = new Configuration().Configure();
sessionFactory = configuration.BuildSessionFactory();
Log.Info("End creating factory");
}
class NestedSessionManager
{
internal static readonly SessionManager sessionManager =
new SessionManager();
}
}
NhibernateHelper, which do some work for start and end session:
public static class NHibernateHelper
{
public static void StartSession()
{
var session = SessionManager.SessionFactory.OpenSession();
session.BeginTransaction();
CurrentSessionContext.Bind(session);
}
public static void EndSession()
{
var session = SessionManager.CurrentSession;
CurrentSessionContext.Unbind(SessionManager.SessionFactory);
if (session != null)
{
try
{
if (session.Transaction != null && session.Transaction.IsActive)
session.Transaction.Commit();
}
catch (Exception ex)
{
session.Transaction.Rollback();
throw new ApplicationException("Error committing database transaction. "+ex.Message, ex);
}
finally
{
session.Close();
session.Dispose();
}
}
}
}
May be my design isn't so good, but I couldn't imagine how can I catch this error.
UPD
Sorry my config. I haven't migrate to fluent yet so:
<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property>
<property name="dialect">NHibernate.Dialect.MySQLDialect</property>
<property name="connection.driver_class">NHibernate.Driver.MySqlDataDriver</property>
<property name="connection.connection_string">***</property>
<property name="show_sql">false</property>
<property name="default_schema">**_***</property>
<property name="current_session_context_class">thread_static</property>
<mapping assembly="***.Core"/>
</session-factory>
</hibernate-configuration>
UPD2
Save method:
public virtual T Save(T entity)
{
NHibernateSession.Save(entity);
return entity;
}
Threadpool callback:
public static void DetectStart(Object threadContext)
{
try
{
var task = (TasksPerAttraction)threadContext;
var startInfo = new ProcessStartInfo(..., ...)
{
UseShellExecute = false,
RedirectStandardOutput = true
};
Process p = Process.Start(startInfo);
var outputXml = p.StandardOutput.ReadToEnd();
p.WaitForExit();
var doc = XDocument.Parse(outputXml);
foreach (var xElement in doc.Root.Descendants("start"))
{
var startDetection = new StartDetection
{
DtStart = DateTime.Parse(xElement.Attribute("startTime").Value),
Attraction = task.Attraction,
};
lock (spinLock)
{
using (var repo = new Repository<StartDetection>(true))
repo.Save(startDetection);
}
}
var tskRepo = new Repository<Task>(true);
foreach(var tsk in task.Tasks)
{
tsk.IsProcessedStart = true;
tskRepo.Update(tsk);
}
tskRepo.Flush();
}
catch (Exception ex)
{
//....
}
}
There are a few potential issues, but I suspect the biggest issue is that you are saving the task.Attraction in one session (so the Attraction for that task is associated to session 1), and then saving the tasks in another session - so you end up with the Attraction in session 1, which is now closed, and session 2 is navigating the relationships to see if it needs to save the Attraction and hence the error. This is a bit of an assumption since I don't have your model or mapping.
I think the easiest fix would be to open the session in your callback, ie:
public static void DetectStart(Object threadContext)
{
try
{
... run your process ...
p.WaitForExit();
// **** OPEN SESSION HERE ****
NHibernateHelper.StartSession();
var doc = XDocument.Parse(outputXml);
foreach (var xElement in doc.Root.Descendants("start"))
{
var startDetection = new StartDetection
{
DtStart = DateTime.Parse(xElement.Attribute("startTime").Value),
Attraction = task.Attraction,
};
lock (spinLock)
{
// *** DON'T CLOSE THE SESSION ON DISPOSE
using (var repo = new Repository<StartDetection>(false))
repo.Save(startDetection);
}
}
// *** DON'T CLOSE THE SESSION ON DISPOSE HERE EITHER!
using(var tskRepo = new Repository<Task>(false))
{
foreach(var tsk in task.Tasks)
{
tsk.IsProcessedStart = true;
tskRepo.Update(tsk);
}
tskRepo.Flush();
}
}
catch (Exception ex)
{
//....
}
finally {
// *** MAKE SURE YOU CLOSE THE SESSION
NHibernateHelper.EndSession();
}
}
Hello Andrew what if you use an IOC to deal with your session? I use ninject and It has been painless also I will share some code that may help you, this is my approximation to solve dealing with Nhibernate, but I am open to suggestions.
The Repo Class:
public class Repositorio<T> : IRepositorio<T> where T : class
{
[Inject]
public ISession Session { get; set; }
#region IRepositorio<T> Members
public IList<T> ListAll()
{
return Session.CreateCriteria<T>().List<T>();
}
public T Look(object id)
{
return Session.Get<T>(id);
}
public void Add(T t)
{
using (ITransaction transaction = Session.BeginTransaction())
{
transaction.Begin();
try
{
Session.Save(t);
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
throw;
}
finally
{
transaction.Dispose();
}
}
}
public void Save(T t)
{
using (ITransaction transaction = Session.BeginTransaction())
{
transaction.Begin();
try
{
Session.SaveOrUpdate(t);
transaction.Commit();
}
catch (Exception)
{
transaction.Rollback();
}
finally
{
transaction.Dispose();
}
}
}
public void Delete(T t)
{
using (ITransaction transaction = Session.BeginTransaction())
{
transaction.Begin();
try
{
Session.Delete(t);
transaction.Commit();
}
catch (Exception e)
{
transaction.Rollback();
Console.WriteLine(e.StackTrace);
}
finally
{
transaction.Dispose();
}
}
}
#endregion
}
My Nhibernate Helper:
public sealed class NHibernateHelper
{
public static ISessionFactory SessionFactory { get; set; }
private static void OpenSession()
{
var configuration = new Configuration();
configuration.Configure();
SessionFactory = configuration.BuildSessionFactory();
}
public static ISession GetCurrentSession()
{
if (SessionFactory == null)
{
OpenSession();
}
if (SessionFactory != null)
return SessionFactory.OpenSession();
return null;
}
public static IStatelessSession GetStatelessSession()
{
if (SessionFactory == null)
{
OpenSession();
}
if (SessionFactory != null)
return SessionFactory.OpenStatelessSession();
return null;
}
public static void CloseSessionFactory()
{
if (SessionFactory != null)
SessionFactory.Close();
}
}
In my Module I do this:
How do I Bind the session to a repo:
Bind<ISession>().ToMethod(c => NHibernateHelper.GetCurrentSession()).InSingletonScope().OnDeactivation(c => NHibernateHelper.CloseSessionFactory());
Note: check the scope of the bind maybe for you is more appropriated the thread scope
How do I bind the Open session with the repo
Bind<SomeRepoImpl>().ToSelf();
hope that this help, I will be glad to help you.
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
NHibernate is throwning an exception when particpating in a distirbuted transaction and you've opened a session by specifying your own connection object.
Unhandled Exception: System.InvalidOperationException: Disconnect cannot be call
ed while a transaction is in progress.
at NHibernate.AdoNet.ConnectionManager.Disconnect()
at NHibernate.Impl.SessionImpl.Close()
at NHibernate.Impl.SessionImpl.Dispose(Boolean isDisposing)
at NHibernate.Transaction.AdoNetWithDistrubtedTransactionFactory.<>c__Display
Class1.<EnlistInDistributedTransactionIfNeeded>b__0(Object sender, TransactionEv
entArgs e)
at System.Transactions.TransactionCompletedEventHandler.Invoke(Object sender,
TransactionEventArgs e)
at System.Transactions.TransactionStatePromotedCommitted.EnterState(InternalT
ransaction tx)
at System.Transactions.InternalTransaction.DistributedTransactionOutcome(Inte
rnalTransaction tx, TransactionStatus status)
at System.Transactions.Oletx.RealOletxTransaction.FireOutcome(TransactionStat
us statusArg)
at System.Transactions.Oletx.OutcomeEnlistment.InvokeOutcomeFunction(Transact
ionStatus status)
at System.Transactions.Oletx.OletxTransactionManager.ShimNotificationCallback
(Object state, Boolean timeout)
at System.Threading._ThreadPoolWaitOrTimerCallback.PerformWaitOrTimerCallback
(Object state, Boolean timedOut)
The following code will reproduce the issue; reference the current FluentNibernate, and NHibernate 2.1.2.4000.
using System;
using System.Data.SqlClient;
using System.Transactions;
using FluentNHibernate.Mapping;
using FluentNHibernate.Cfg.Db;
using FluentNHibernate.Cfg;
namespace ConsoleApplication2
{
class Program
{
static void Main(string[] args)
{
var cfg =
Fluently.Configure().Database(
MsSqlConfiguration.MsSql2008.ConnectionString("Integrated Security=SSPI;Data Source=.;Initial Catalog=Test").DefaultSchema("dbo")
).Mappings(x => x.FluentMappings.AddFromAssemblyOf<MyTableMap>()).BuildConfiguration();
using (var sf = cfg.BuildSessionFactory())
{
using (var ts = new TransactionScope().PromoteToDtc())
{
using (var conn = new SqlConnection("Integrated Security=SSPI;Data Source=.;Initial Catalog=Test"))
{
conn.Open();
using (var session = sf.OpenSession(conn))
{
session.Save(new MyTable { String = "Hello!" });
}
}
ts.Complete();
}
Console.WriteLine("It saved!");
Console.ReadLine();
}
}
}
public class DummyEnlistmentNotification : IEnlistmentNotification
{
public static readonly Guid Id = new Guid("E2D35055-4187-4ff5-82A1-F1F161A008D0");
public void Prepare(PreparingEnlistment preparingEnlistment)
{
preparingEnlistment.Prepared();
}
public void Commit(Enlistment enlistment)
{
enlistment.Done();
}
public void Rollback(Enlistment enlistment)
{
enlistment.Done();
}
public void InDoubt(Enlistment enlistment)
{
enlistment.Done();
}
}
public static class TSExetensions
{
public static TransactionScope PromoteToDtc(this TransactionScope scope)
{
Transaction.Current.EnlistDurable(DummyEnlistmentNotification.Id, new DummyEnlistmentNotification(), EnlistmentOptions.None);
return scope;
}
}
public class MyTable
{
public virtual int Id { get; private set; }
public virtual string String { get; set; }
}
public sealed class MyTableMap : ClassMap<MyTable>
{
public MyTableMap()
{
Id(x => x.Id).GeneratedBy.Native();
Map(x => x.String).Not.Nullable();
}
}
}
The issue is indeed with NHiberate. They're working on a patch. For reference, the issue can be found here