A similar question was asked here but had no answer.
I am attempting to use a System.Transactions.CommittableTransaction with EF CTP4 and SQL CE 4.
I have created the following transaction attribute for my ASP.NET MVC Controller actions:
public class TransactionAttribute : ActionFilterAttribute
{
CommittableTransaction transaction;
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
transaction = new CommittableTransaction();
Transaction.Current = transaction;
base.OnActionExecuting(filterContext);
}
public override void OnResultExecuted(ResultExecutedContext filterContext)
{
base.OnResultExecuted(filterContext);
try
{
var isValid = filterContext.Exception == null || filterContext.ExceptionHandled;
if (filterContext.Controller.ViewData.ModelState.IsValid && isValid) {
transaction.Commit();
} else {
transaction.Rollback();
Transaction.Current = null;
}
}
finally
{
transaction.Dispose();
}
}
}
When I use this filter I get the error:
System.InvalidOperationException: The connection object can not be enlisted in transaction scope.
However, the following test passes:
[Test]
public void Transaction_rolls_back_if_exception()
{
var transaction = new CommittableTransaction();
Transaction.Current = transaction;
try
{
var project = new Project { Title = "Test" };
projectRepo.SaveOrUpdate(project);
context.SaveChanges();
var post = new Post { Title = "Some post" };
blogRepo.SaveOrUpdate(post);
throw new Exception();
context.SaveChanges();
transaction.Commit();
}
catch (Exception ex)
{
transaction.Rollback();
Transaction.Current = null;
}
projectRepo.GetAll().Count().ShouldEqual(0);
blogRepo.GetAll().Count().ShouldEqual(0);
}
Has this something to do with how I am initializing the DbContext?
I ran into this same issue. You cannot use Transactions with the CE Connection. I ended up having my datacontext manage my ce connection and implementing a unit of work pattern to hold my actions, then executing all the scheduled actions inside a SqlCeTransaction then calling commit or rollback myself.
http://msdn.microsoft.com/en-us/library/csz1c3h7.aspx
Related
I've been tasked with taking over an existing ASP.NET MVC 2.0 web application that was developed by a third party developer who is no longer around to provide any assistance. There has been a requirement to add some functionality to the project, which required a project upgrade to .NET 4.5, which has been performed.
The sites underlying MSSQL 2008 R2 database access has been implemented using NHibernate version 2.0.1.4000, along with Castle and FluentNHibernate.
This is the first project I've been involved in that has used NHibernate, and I've hit a problem that has me stumped. The problem did not exist until the upgrade to .NET 4.5.
All database operations are working normally, except for one. Saving a particular object (
Opportunity type) to the database (this object directly maps to an Opportunity database table) fails. Prior to saving (in this case a SQL UPDATE statement), the object has new values set. But the record in the database always has the old values after saving.
Hooking up log4net to view the debug code, shows that the record is indeed updated, but using the old values in the UPDATE statement.
Surprisingly, the Opportunity object is intially saved using the same Save method (albeit via a different action method), and that is saving to the database just fine.
So my question is, what would cause this to happen? Being that I'm not an NHibernate expert, is it the case that the NHibernate version is simply incompatible with .NET 4.5? Or can anyone provide a pointer as to what the problem might be? I'm happy to show any code, but as there is so much I would need to know what. Below is a starter:
The Global.asax has the following references to NHibernate:
private static void MvcApplication_BeginRequest(object sender, System.EventArgs e)
{
NHibernateSessionManager.Instance.BeginTransaction();
}
private static void MvcApplication_EndRequest(object sender, System.EventArgs e)
{
NHibernateSessionManager.Instance.CommitTransaction();
}
The NHibernateSessionManager class is defined as (Opportunity derives from DomainBase):
public sealed class NHibernateSessionManager
{
private ISessionFactory sessionFactory;
private Configuration config;
#region Thread-safe, lazy Singleton
public static NHibernateSessionManager Instance
{
get
{
return Nested.nHibernateSessionManager;
}
}
private NHibernateSessionManager()
{
InitSessionFactory();
}
private class Nested
{
internal static readonly NHibernateSessionManager nHibernateSessionManager = new NHibernateSessionManager();
}
#endregion
private void InitSessionFactory()
{
var autoMappings = AutoPersistenceModel.MapEntitiesFromAssemblyOf<DomainBase>()
.Where(type =>
typeof(DomainBase).IsAssignableFrom(type) &&
type.IsClass &&
!type.IsAbstract)
.WithSetup(s =>
{
s.IsBaseType = type =>
type == typeof (DomainBase);
})
.UseOverridesFromAssemblyOf<OpportunityMappingOverride>()
.ConventionDiscovery.Add(DefaultLazy.AlwaysTrue())
.ConventionDiscovery.Add<CascadeAllHasOneConvention>()
.ConventionDiscovery.Add<CascadeAllHasManyConvention>()
.ConventionDiscovery.Add<CascadeAllReferenceConvention>();
sessionFactory = Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2005
.ConnectionString(c => c.FromConnectionStringWithKey("Default"))
.UseReflectionOptimizer()
.Cache(c => c.UseQueryCache().UseMininmalPuts().ProviderClass<SysCacheProvider>())
.ShowSql())
.Mappings(m => m.AutoMappings.Add(autoMappings))
.ExposeConfiguration(SetConfiguration)
.BuildSessionFactory();
}
private void SetConfiguration(Configuration cfg)
{
config = cfg;
}
public void RegisterInterceptor(IInterceptor interceptor)
{
ISession session = threadSession;
if (session != null && session.IsOpen)
{
throw new CacheException("You cannot register an interceptor once a Session has already been opened");
}
GetSession(interceptor);
}
public void GenerateSchema()
{
new SchemaExport(config).Execute(false, true, false, false);
}
public ISession GetSession()
{
return GetSession(null);
}
private ISession GetSession(IInterceptor interceptor)
{
ISession session = threadSession;
if (session == null)
{
if (interceptor != null)
{
session = sessionFactory.OpenSession(interceptor);
}
else
{
session = sessionFactory.OpenSession();
}
threadSession = session;
}
return session;
}
public void CloseSession()
{
ISession session = threadSession;
threadSession = null;
if (session != null && session.IsOpen)
{
session.Close();
}
}
public void BeginTransaction()
{
ITransaction transaction = threadTransaction;
if (transaction == null)
{
transaction = GetSession().BeginTransaction();
threadTransaction = transaction;
}
}
public void CommitTransaction()
{
ITransaction transaction = threadTransaction;
try
{
if (transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack)
{
transaction.Commit();
threadTransaction = null;
}
}
catch (HibernateException)
{
RollbackTransaction();
throw;
}
}
public void RollbackTransaction()
{
ITransaction transaction = threadTransaction;
try
{
threadTransaction = null;
if (transaction != null && !transaction.WasCommitted && !transaction.WasRolledBack)
{
transaction.Rollback();
}
}
finally
{
CloseSession();
}
}
private static ITransaction threadTransaction
{
get
{
return (ITransaction)CallContext.GetData("THREAD_TRANSACTION");
}
set
{
CallContext.SetData("THREAD_TRANSACTION", value);
}
}
private static ISession threadSession
{
get
{
return (ISession)CallContext.GetData("THREAD_SESSION");
}
set
{
CallContext.SetData("THREAD_SESSION", value);
}
}
}
I'm hoping I won't get shot down for this question being too general. I've spent a day trying to work out what is happening, including extensive searches online.
It turned out the problem was that the NHibernateSessionManager class was storing its ITransaction and ISession objects in System.Runtime.Remoting.Messaging.CallContext.
Swapping it out to store the objects in the HttpContext.Current.Items collection resolved the issue.
I found this post which implies .NET 4.5 handles CallContext slightly differently compared with previous versions, which obviously caused my issue.
Because the NHibernateSessionManager class was in a class library that was also used by a couple of rarely used console applications, I left a fallback to the CallContext object as per below (not pretty, and there might have been an better alternative, but worked for me [subject to testing], as I've spent far to long figuring this one out using remote debugging):
private static ITransaction threadTransaction
{
get
{
try
{
return (ITransaction)System.Web.HttpContext.Current.Items["THREAD_TRANSACTION"];
}
catch
{
return (ITransaction)CallContext.GetData("THREAD_TRANSACTION");
}
}
set
{
try
{
System.Web.HttpContext.Current.Items["THREAD_TRANSACTION"] = value;
}
catch
{
CallContext.SetData("THREAD_TRANSACTION", value);
}
}
}
private static ISession threadSession
{
get
{
try
{
return (ISession)System.Web.HttpContext.Current.Items["THREAD_SESSION"];
}
catch
{
return (ISession)CallContext.GetData("THREAD_SESSION");
}
}
set
{
try
{
System.Web.HttpContext.Current.Items["THREAD_SESSION"] = value;
}
catch
{
CallContext.SetData("THREAD_SESSION", value);
}
}
}
I have a small project in asp.net mvc 3 and I am using RavenDB to store data.
But when i am trying to update entity i have error saying
"Attempted to associate a different object with id 'orders/257'"
I have a service class to menage entities.
This is method to update entity called Order.
I'v omitted the rest of methods baceuse of clarity
public ErrorState UpdateOrder(Order order)
{
try
{
documentSession.Store(order);
documentSession.SaveChanges();
return new ErrorState { Success = true };
}
catch (Exception ex)
{
return new ErrorState { Success = false, ExceptionMessage = ex.Message };
}
}
This is rest of OrderRepository
private readonly IDocumentSession documentSession;
public OrderRepository(IDocumentSession _documentSession)
{
documentSession = _documentSession;
}
ErrorState class is for menagege errors in app, it contains bool success and string message of exception.
This is my edit Actions.
public ActionResult Edit(int id)
{
Order order = orderRepository.ObtainOrder(id);
if (order == null)
{
TempData["message"] = string.Format("Order no: {0} not found", id);
return RedirectToAction("Index");
}
return View(order);
}
[HttpPost]
public ActionResult Edit(Order order)
{
if(!ModelState.IsValid)
return View();
errorState = orderRepository.UpdateOrder(order);
if (errorState.Success)
{
TempData["message"] = string.Format("Order no: {0} has been changed", order.Id);
return RedirectToAction("Index");
}
else
{
TempData["Message"] = string.Format("Error on update order no: {0} MSG: {1}", order.Id,errorState.ExceptionMessage);
return RedirectToAction("Index");
}
}
This is the rest of the controller , I'v omitted the rest of actions baceuse of clarity.
private readonly IOrderRepository orderRepository;
private ErrorState errorState;
public HomeController(IOrderRepository _orderRepository,IDocumentSession _documentSession)
{
orderRepository = _orderRepository;
}
You already have an instance of an order with that id.
Check the session lifetime, is it possible that you have the same session across requests?
I have a controller method that returns a void because it is building an Excel report for the user to download. The Excel 3rd party library we're using is writing to the response itself. The method looks something like this:
[HttpGet]
public void GetExcel(int id)
{
try
{
var report = _reportService.GetReport(id);
var table = _reportService.GetReportTable(id);
var excelReport = new ExcelReport(table, report.Name);
excelReport.DownloadReport(System.Web.HttpContext.Current.Response);
}
catch (Exception ex)
{
// This is wrong, of course, because I'm not returning an ActionResult
Response.RedirectToRoute("/Report/Error/", new { exceptionType = ex.GetType().Name });
}
}
There are several security checks in place that throw exceptions if the user doesn't meet certain credentials for fetching the report. I want to redirect to a different page and pass along some information about the exception, but I can't figure out how to do this in MVC3....
Any ideas?
You could use the following code
Response.Redirect(Url.Action("Error", "Report", new { exceptionType = ex.GetType().Name }));
But have you taken a look at the FilePathResult or FileStreamResult ?
Instead of letting the 3rd part library write to the response directly get the content use regular ActionResult and return File(...) for the actual file or RedirectToAction(...) (or RedirectToRoute(...)) on error. If your 3rd party library can only write to Response you may need to use some tricks to capture it's output.
[HttpGet]
public ActionResult GetExcel(int id)
{
try
{
var report = _reportService.GetReport(id);
var table = _reportService.GetReportTable(id);
var excelReport = new ExcelReport(table, report.Name);
var content = excelReport.MakeReport(System.Web.HttpContext.Current.Response);
return File(content, "application/xls", "something.xls");
}
catch (Exception ex)
{
RedirectToRoute("/Report/Error/", new { exceptionType = ex.GetType().Name });
}
}
You can return an EmptyActionResult:
[HttpGet]
public ActionResult GetExcel(int id)
{
try
{
var report = _reportService.GetReport(id);
var table = _reportService.GetReportTable(id);
var excelReport = new ExcelReport(table, report.Name);
excelReport.DownloadReport(System.Web.HttpContext.Current.Response);
return new EmptyResult();
}
catch (Exception ex)
{
return RedirectToAction("Error", "Report", rnew { exceptionType = ex.GetType().Name });
}
}
Not sure if it works, haven't tested it.
Another approach would be using an exception filter:
public class MyExceptionFilter : FilterAttribute, IExceptionFilter
{
public void OnException(ExceptionContext filterContext)
{
var routeValues = new RouteValueDictionary()
{
{ "controller", "Error" },
{ "action", "Report" }
};
filterContext.Result = new RedirectToRouteResult(routeValues);
filterContext.ExceptionHandled = true;
// Or I can skip the redirection and render a whole new view
//filterContext.Result = new ViewResult()
//{
// ViewName = "Error"
// //..
//};
}
}
I am trying to use Fluent NHibernate with ASP.NET MVC 3 and I cannot seem to find a tutorial that explains how to get it all configured with ASP.NET MVC. I mainly am wondering where to put the ISession building function and how to call it when I need it. I see so many different implementations but none of them specify where they put this code. So if anyone can explain how to get it all configured to work with MVC 3 or where a very detailed tutorial is, that would be greatly appreciated.
You can have a look at S#arp Architecture.
It's a pretty solid architectural framework to work with ASP.NET MVC & NHibernate. They have a decent documentation and there's some sample projects to look at.
http://www.sharparchitecture.net/
If you are not using dependency injection you can try something like this
public class MvcApplication : System.Web.HttpApplication
{
public static ISession CurrentSession
{
get { return (ISession)HttpContext.Current.Items["current.session"]; }
set { HttpContext.Current.Items["current.session"] = value; }
}
private static ISessionFactory _session_factory;
private static object _session_factory_lock = new object();
protected static ISessionFactory CreateSessionFactory()
{
if (_session_factory != null) return _session_factory;
if (ConfigurationManager.ConnectionStrings["DbConnection"] != null)
{
var conn = ConfigurationManager.ConnectionStrings["DbConnection"];
SqlServerSessionFactoryBuilder fb = new SqlServerSessionFactoryBuilder(conn.ConnectionString);
_session_factory = fb.GetSessionFactory();
return _session_factory;
}
throw new Exception("Cannot build session factory, connection string is not defined.");
}
public MvcApplication()
{
_session_factory = CreateSessionFactory();
BeginRequest += delegate
{
try
{
CurrentSession = _session_factory.OpenSession();
}
catch (FluentConfigurationException ex)
{
logger.FatalException(string.Format("Error configuring the database {0}", ex.Message), ex);
}
};
EndRequest += delegate
{
if (CurrentSession != null)
{
if (CurrentSession.Transaction != null && CurrentSession.Transaction.IsActive)
{
logger.Error("Rolling back uncommited transaction");
CurrentSession.Transaction.Rollback();
}
else
{
CurrentSession.Flush();
}
CurrentSession.Close();
}
};
Error += delegate
{
var error = this.Server.GetLastError();
logger.ErrorException(string.Format("Unhandled error : {0}", error.Message), error);
};
}
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
RegisterRoutes(RouteTable.Routes);
}
}
What is the best approach to managing NHibernate transaction using Autofac within web application?
My approach to session is
builder.Register(c => c.Resolve<ISessionFactory>().OpenSession())
.ContainerScoped();
For ITransaction, I have found an example on Google Code, but it relies on HttpContext.Current.Error when deciding whether to rollback.
Is there a better solution? And what scope NHibernate transaction should have?
I posted this a while ago:
http://groups.google.com/group/autofac/browse_thread/thread/f10badba5fe0d546/e64f2e757df94e61?lnk=gst&q=transaction#e64f2e757df94e61
Modified, so that the interceptor has logging capability and [Transaction] attribute can also be used on a class.
[global::System.AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
public class TransactionAttribute : Attribute
{
}
public class ServicesInterceptor : Castle.Core.Interceptor.IInterceptor
{
private readonly ISession db;
private ITransaction transaction = null;
public ServicesInterceptor(ISession db)
{
this.db = db;
}
public void Intercept(IInvocation invocation)
{
ILog log = LogManager.GetLogger(string.Format("{0}.{1}", invocation.Method.DeclaringType.FullName, invocation.Method.Name));
bool isTransactional = IsTransactional(invocation.Method);
bool iAmTheFirst = false;
if (transaction == null && isTransactional)
{
transaction = db.BeginTransaction();
iAmTheFirst = true;
}
try
{
invocation.Proceed();
if (iAmTheFirst)
{
iAmTheFirst = false;
transaction.Commit();
transaction = null;
}
}
catch (Exception ex)
{
if (iAmTheFirst)
{
iAmTheFirst = false;
transaction.Rollback();
db.Clear();
transaction = null;
}
log.Error(ex);
throw ex;
}
}
private bool IsTransactional(MethodInfo mi)
{
var atrClass = mi.DeclaringType.GetCustomAttributes(false);
foreach (var a in atrClass)
if (a is TransactionAttribute)
return true;
var atrMethod = mi.GetCustomAttributes(false);
foreach (var a in atrMethod)
if (a is TransactionAttribute)
return true;
return false;
}
}
When I use autofac I use the same container scoped method but instead of passing the same session to my Repository/DAO objects I pass an UnitOfWork that is container scoped. The Unit of work has this in the constructor.
private readonly ISession _session;
private ITransaction _transaction;
public UnitOfWork(ISession session)
{
_session = session;
_transaction = session.BeginTransaction();
}
And the dispose is:
public void Dispose()
{
try
{
if (_transaction != null &&
!_transaction.WasCommitted &&
!_transaction.WasRolledBack)
_transaction.Commit();
_transaction = null;
}
catch (Exception)
{
Rollback();
throw;
}
}
I am (ab)using the deterministic disposal stuff in autofac in order to manage this, and well I sort of like it.
The other thing is that I am basically only targeting an ASPNet environment and made a conscious decision that a transaction is tied to a web request. So a transaction per web request pattern.
Because of that I can do this error handling code in an IHttpModule:
void context_Error(object sender, System.EventArgs e)
{
_containerProvider.RequestContainer.Resolve<IUnitOfWork>().Rollback();
}
I haven't taken a look at NHibernate.Burrow too closely but I'm sure there is something there that does most of this.
I usually manage the transaction myself..
public ActionResult Edit(Question q){
try {
using (var t = repo.BeginTransaction()){
repo.Save(q);
t.Commit();
return View();
}
catch (Exception e){
...
}
}