I use EF CTP5 on my last project together with NCommon and its UnitOfWork, Repositories, IoC and DI abstracted by MS P&P team and StructureMap as the concrete implemetation of IoC and DI.
Problem :
Let’s have the following example (I took the example from the NCommon unit tests and extend it to do the same Linq query twice):
using (var scope = new UnitOfWorkScope())
{
var repository = new EFRepository<User>();
User user;
user = repository.Where(u => u.ComsiID == "login").FirstOrDefault();
user = repository.Where(u => u.ComsiID == "login").FirstOrDefault();
}
Just for the completion: creating new UnitOfWorkScope starts the DB transaction. In the above example, using UnitOfWork we create and start the transaction. Then we create new EFRepository instance which creates the ObjectContext. ObjectContext uses the connection identified by the same name AppDb and handles the connection on its own. Then we query the DB. EF opens the connection (this implies that the connection is enlisted into the current running transaction), reads the data and then close the connection. Fine! But then next (in this demo case) same query does the same: opens the connection. Because we run inside the transaction, so the connection is also enlisted into the transaction. And because there was already enlisted connection the transaction mechanism propagates it into DTS.
The result of the above code is that it works and sometimes not! The exception is following:
System.Data.EntityException: The underlying provider failed on Open.
---> System.InvalidOperationException: The connection object can not be enlisted in transaction scope.
THE PROBLEM IS IN ENTITY FRAMEWORK!!!!! and the result of the test depends on the underlying DB. If you use SQLExpress edition (as is used by default in NCommon unit tests), everything works fine! SQLExpress can not elevate the transaction to MS DTC. But if you change the connection to the real SQL server you starts to have the problems.
The problem is that in case you don’t send the concrete connection instance to EF, EF handles the connection status on his own => closes as soon as possible. I understand it but in case of Transaciton is fails! It would be good to have there a switch to configure that behavior. Anyway, the reality is different so how to fix it.
The solution is to manage the connection by your own and while creating the ObjectContext pass already created and open connection. You can find many examples about it on the internet.
Hm, fine but … not so nice. You need manually on all places where ObjectContext, or your class derived from ObjectContext, is used to manage the connection
With NCommon and it’s configuration capabilities you can do the following:
NCommon.Configure.Using(adapter)
.ConfigureData<EFConfiguration>(config => config.WithObjectContext(
() =>
{
AppDb db = new AppDb();
if (Transaction.Current != null)
{
db.Connection.Open();
}
return db;
}))
.ConfigureUnitOfWork<DefaultScopeUnitOfWorkConfiguration>();
So you don’t need to do open the connection over and over and on different places. Just configure NCommon on one place and use it. That’s the power of the centralized factory code-enabled configurations. In this case, thanks to StructureMap and NCommon!
Here is my full unit test class (using MS Tests):
[TestClass]
public class SimpleTests
{
public IServiceLocator MyServiceLocator;
[TestInitialize]
public void Setup()
{
var _state = new FakeState();
var _unitOfWorkFactory = new EFUnitOfWorkFactory();
var _connectionString = ConfigurationManager.ConnectionStrings["AppDb"].ConnectionString;
_unitOfWorkFactory.RegisterObjectContextProvider(
() =>
{
var ctx = new AppDb(_connectionString);
if (Transaction.Current != null)
{
ctx.Connection.Open();
}
return ctx;
});
var _locator = MockRepository.GenerateStub<IServiceLocator>();
_locator.Stub(x => x.GetInstance<IUnitOfWorkFactory>()).Return(_unitOfWorkFactory);
_locator.Stub(x => x.GetInstance<IState>()).Do(new Func<IState>(() => _state));
ServiceLocator.SetLocatorProvider(() => _locator);
}
[TestCleanup]
public void Cleanup()
{
}
[TestMethod]
public void Can_perform_simple_query()
{
using (var scope = new UnitOfWorkScope())
{
var repository = new EFRepository<User>();
User user;
user = repository.Where(u => u.ComsiID == "a").FirstOrDefault();
user = repository.Where(u => u.ComsiID == "a").FirstOrDefault();
}
}
I must again say, NCommon and StructureMap rocks!
Happy coding…