Cannot use distributed transactions while providing connection to the session
Description
Environment
Attachments
Activity
Julian Maughan December 1, 2010 at 8:06 AM
Committed to trunk (r5285).
Julian Maughan November 29, 2010 at 7:41 PM
Thanks for letting me know. I will apply the same change to the trunk before I resolve this issue. I would also like to provide a test for the problem, but the issue I found is that the exception is thrown on a thread-pool thread, and isn't visible to NUnit.
Andy Johnstone November 29, 2010 at 6:49 AM
Julian,
Thanks for the 2.1.2 update. I've applied it to the 2.1.2 source and updated our application, and it does seem to resolve the issue.
Julian Maughan November 24, 2010 at 6:31 PM
I've attached the 2.1.2-GA version of AdoNetWithDistrubtedTransactionFactory.cs with the proposed change (at line 39). Sorry there is no patch, but I couldn't find the 2.1.2.GA tag/branch in source control.
Andy Johnstone November 23, 2010 at 5:19 PM
Julian, I would appreciate a patch against the 2.1.2 version. It looks like the patch contains two sets of changes to that file, and I want to make sure it's exactly right.
It's not possible to use distributed transactions with sessions where you have provided your own connection. Attempting to do so always results in an exception: "System.InvalidOperationException: Disconnect cannot be called while a transaction is in progress."
The following code reproduces the issue on .Net 4, MS Sql Server 2008 R2, the current Fluent NHibernate. Haven't tried it on other platforms.
Note that either 1) using the OpenSession() which takes no arguements or 2) not using a distributed transaction will cause the program to complete successfully. We can't do the latter, as our code runs in NServiceBus, and the former is going to cause a major rework (we have the same schema across many databases / servers), which will include creating a session factory per connection essentially, and on the fly.
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();
}
}
}