TransactionPromotionException accessing Model from new Module

Topics: Customizing Orchard, Troubleshooting, Writing modules
Jun 14, 2011 at 10:02 PM

Hello,

I'm getting a TransactionPromotionException when accessing my own Model from an MVC View. 

There was an error promoting the transaction to a distributed transaction.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. 

Exception Details: System.Transactions.TransactionPromotionException: There was an error promoting the transaction to a distributed transaction.

[TransactionPromotionException: There was an error promoting the transaction to a distributed transaction.]
   System.Data.SqlServerCe.SqlCeDelegatedTransaction.Promote() +28
   System.Transactions.TransactionStatePSPEOperation.PSPEPromote(InternalTransaction tx) +69
   System.Transactions.TransactionStateDelegatedBase.EnterState(InternalTransaction tx) +190

 

Some background info... I'm "modularizing" an exising MVC3 project into an Orchard Module. The plan is to use Orchard for the front end, theming, navigation, users, etc. The module is custom application that has it's own data provider via MSLinqToSQL to SQL Server 2008. The page loads fine when not run under Orchard (as a stand alone MVC3 web app), but when run as a module it blows up when accessing the Model.  Orchard is running locally with SQL CE (which from the stack trace SQLCE may be the choke point). 

Any one have any pointers? 

Coordinator
Jun 14, 2011 at 10:12 PM

Did you opt out of the ambient Orchard transaction?

Jun 14, 2011 at 10:17 PM

Negative... 

Is there a reference to that somewhere?

Coordinator
Jun 14, 2011 at 10:27 PM

More information in this thread: http://orchard.codeplex.com/discussions/244312. In particular, TransactionScopeOption.Suppress.

Jun 14, 2011 at 10:44 PM

I'm no expert, tried the example in that thread but no dice. 

I tried to wrap where i set my linq2sql data context like this

using (new TransactionScope(TransactionScopeOption.Suppress)) { _dataContext = new MyDataContext(connectionString);}

Doesn't work, also tried it with the _dataContext.Connection.Open(), but I was under the impression that the connection doesn't need to be forced open, only opened when the context needs it (at render time in this instance) or something like that.

Coordinator
Jun 14, 2011 at 10:46 PM

Hold on. How can something in a view have anything to do with the database?

Jun 14, 2011 at 10:52 PM

The model being passed down to my view is a type of IEnumerable<SomeClass> SomeClass being an autogenerated class from Linq2sql

Coordinator
Jun 14, 2011 at 11:18 PM

Why would you use something that does database requests, from the view?

Jun 14, 2011 at 11:25 PM

Following Microsoft's tutorial for using Linq2Sql w/ ASP.NET MVC http://www.asp.net/mvc/tutorials/creating-model-classes-with-linq-to-sql-cs see section "Using the Repository Pattern", that's a rough example of how our code is used. Lazy loaded Linq2Sql objects.

 

We thought it would be a good practice to Follow Microsoft tutorials 

Coordinator
Jun 14, 2011 at 11:28 PM

Not always. Some Microsoft tutorials show bad practice, unfortunately. That tutorial should show how to use view models, disconnected from the database.

Jun 14, 2011 at 11:32 PM

Ok, so what's the work around without having to completely re-write the existing MVC3 app? 

 

Is the conflict coming because Orchard has a transaction scope open when my views are trying to access it's context?

Coordinator
Jun 14, 2011 at 11:45 PM

I can't be sure until you try, but I think yes, the problem is the Orchard transaction trying to handle your Linq2SQL request and failing. If you followed that article to the end, your data access code should be restricted to the repository (even if there is some lazy loading involved). I think you should be able, from the controller, to surround the code that accesses the repo with the using that opts out of the ambient transaction, and map what comes out of the repository onto a proper view model that is not attached in any way to the database. You can then pass that to the view.

Coordinator
Jun 14, 2011 at 11:46 PM

From your controller, instead of passing Foo to the View model, pass Foo.ToList(). It will do the query from the controller, the result is still an IEnumerable.

Jun 15, 2011 at 12:20 AM

Hold on. How can orchard need a database transaction from the view ? see what i did there :)

I got it working... let me rule out what actually fixed it and report back.

Coordinator
Jun 15, 2011 at 12:23 AM

The transaction is around the request, not around the view. Nice try. :)

Jun 15, 2011 at 12:49 AM
Edited Jun 15, 2011 at 12:50 AM

So i followed both Sebastien's suggestion and Bertrand's in order to avoid that error

 

List<Car> cars;
            
using (new TransactionScope(TransactionScopeOption.Suppress)) {
     cars = _repository.GetAllCars().ToList();
}
return View(cars);

 

What's happening is that returning the list is actually fetching the data for the simple properties, so my view doesn't have to.

However in the view if we do something like

 

@foreach (var car in Model){
     <h2>car.Name</h2>
     foreach (var passenger in car.Passengers {
<p>passenger.Name</p>
}
}

 

It will trigger the same exception as before as it now has to query the db for the passenger model. 

I'm going to try to install SQL Express and setup this orchard instance on that to see if it's just the SQLCE causing the issue.

Coordinator
Jun 15, 2011 at 12:52 AM

Right, and that code in the view is not opting out of the transaction, so it suffers from the same problem. That's one more reason to have a view model that is completely detached from the DB.

Jun 15, 2011 at 1:09 AM

Yea, so there's no issue accessing lazy loaded properties of the Linq2sql object when running Orchard on SQL Express, only when Orchard is running SQLCE.

Issue is something like -> SQLCE does not support distributed transactions, and LINQ to SQL tries to promote the existing transaction on SQLCE to distributed, or something.  

Jun 15, 2011 at 1:28 AM
Edited Jun 15, 2011 at 1:32 AM
bertrandleroy wrote:

The transaction is around the request, not around the view. Nice try. :)

I'm trying to learn a bit... Hmm, so if the transaction is around the request, why does the view have access to it (and the SQLCE transaction), and why does it seem like the root of this issue is caused by SQLCE. Somehow the existing transaction for the request must related to SQL in some way? 

(edited for clarity or something)

Coordinator
Jun 15, 2011 at 5:32 AM

Yes, different database engines, different constraints. Many things happen during the request, one of them is the rendering. That doesn't make it right to do database access from the view. The root of the issue is not SQL CE, it's that you are trying to access two different databases from the same transaction. This is why opting out of the ambient transaction fixes the problem.

Jun 15, 2011 at 6:49 PM
Edited Jun 15, 2011 at 7:08 PM

I have exactly the same problem with a external service dll which uses Entity Framework code first to get it's data. I'm getting also the error: 

There was an error promoting the transaction to a distributed transaction.

Is there already an solution found for this problem?

EDIT:

Ok i got i finally working with indeed:

using (new TransactionScope(TransactionScopeOption.Suppress)) {
     ... external access.....
}

Thanks guys!