When using EntityFramework, you need a consistent way to manage the context object lifecycle. The most accepted way to do this is creating a context per HTTP Request
I´m going to show you a basic thread-safe implementation.
Building the component
Context Resolver Contract
public interface IContextResolver
{
TContext GetCurrentContext<TContext>() where TContext : DbContext;
void ReleaseContext();
}
This interface is really simple, it just defines two methods.
The
GetCurrentContext
method will be used to get or create the EntityFramework context object.The
ReleaseContext
method will be in charge to callDispose
in your context in order to release resources.
Context Resolver implementation
public class ContextResolver : IContextResolver
{
private static object syncRoot = new Object();
private string connectionString;
private volatile HttpContextBase httpContextBase;
private const string ContextItemName = "Context_Per_Request";
public ContextResolver(HttpContextBase httpContext, string connectionString)
{
Condition.Requires(httpContext).IsNotNull();
Condition.Requires(connectionString).IsNotNullOrWhiteSpace();
this.connectionString = connectionString;
this.httpContextBase = httpContext;
}
public TContext GetCurrentContext<TContext>() where TContext : DbContext
{
TContext context = default(TContext);
lock (syncRoot)
{
context = this.httpContextBase.Items[ContextItemName] as TContext;
}
if (context == null)
{
lock (syncRoot)
{
if (context == null)
{
context = (TContext)Activator.CreateInstance(typeof(TContext), this.connectionString);
this.httpContextBase.Items.Add(ContextItemName, context);
}
}
}
Condition.Ensures(context).IsNotNull().IsOfType(typeof(TContext));
return context;
}
public void ReleaseContext()
{
DbContext context = default(DbContext);
lock (syncRoot)
{
context = this.httpContextBase.Items[ContextItemName] as DbContext;
}
if (context != null)
{
context.Dispose();
}
}
}
The implementation is straightforward, the only tricky part is the addition of lock
statements when reading and writing the EntityFramework context.
The C# specification clearly defines what operations are "atomic" in section 5.5. The atomic operations are reads and writes of variables of any reference type, or, effectively, any built-in value type that takes up four bytes or less
In order to implement the context per HTTP request pattern, we use HttpContext.Items. The objects we store in this dictionary, will be accessible only during the current HTTP request.
Gets a key/value collection that can be used to organize and share data between an IHttpModule interface and an IHttpHandler interface during an HTTP request.
Usage
You just have to configure your DI container (if any) to inject the correct instance of IContextResolver
.
Injecting
For simplicity I will inject it in my MVC controller
private IContextResolver contextResolver;
public HomeController(IContextResolver contextResolver)
{
this.contextResolver = contextResolver;
}
public ActionResult SimpleData()
{
var ctx = this.contextResolver.GetCurrentContext<pubsEntities>();
return View(ctx.jobs);
}
In Global.asax
During the whole request, all the calls to IContextResolver.GetCurrentContext
are going to return the same context instance, we now need to dispose the context after the request has been processed. We can do this in the PostRequestHandlerExecute
application event in the Global.asax file.
public MvcApplication()
{
this.PostRequestHandlerExecute += MvcApplication_PostRequestHandlerExecute;
}
void MvcApplication_PostRequestHandlerExecute(object sender, EventArgs e)
{
var resolver = ServiceLocator.Current.GetInstance<IContextResolver>();
resolver.ReleaseContext();
}
Notice the use of the ServiceLocator
... your first impression might be: “hey dude, that’s an anti-pattern”...
Most of the time it is an anti-pattern, but in this case, we are using it inside the Composition Root Object of the Web Application, therefore we are allowed to use the container, actually I could change the code to use directly my DI container (Ninject - Kernel in this case), but this will get me the same result, even though, I would be adding an explicit dependency to Ninject.
Unit Testing
It’s time to show you the unit tests I created for this small but useful component
Builder
I tend to use builders to create my SUT objects, it seems like a consistent way to create testing objects
public class ConcreteResolverBuilder
{
private ContextResolver Instance { get; set; }
private static volatile HttpContextBase httpContextBase;
private static volatile Dictionary<string, object> items;
private static object syncRoot = new Object();
public static HttpContextBase HttpContextBase
{
get
{
if (httpContextBase == null)
{
lock (syncRoot)
{
if (httpContextBase == null)
{
var mock = new Mock<HttpContextBase>();
items = new Dictionary<string, object>();
mock.Setup(x => x.Items).Returns(items);
httpContextBase = mock.Object;
}
}
}
httpContextBase.Should().NotBeNull();
return httpContextBase;
}
}
public ConcreteResolverBuilder Initialize()
{
this.Instance = new ContextResolver(
HttpContextBase,
ConfigurationManager.ConnectionStrings["Msts"].ConnectionString);
return this;
}
public ContextResolver Build()
{
this.Instance.Should().NotBeNull();
return this.Instance;
}
public static implicit operator ContextResolver(ConcreteResolverBuilder builder)
{
return builder.Build();
}
}
I´m implementing a singleton pattern just because I want to mock an HttpContextBase
object to simulate a real HTTP request
I´m also configuring the HttpContextBase.Items
properties with Moq to return a Dictionary
object
Note that since my intention is to simulate concurrent threads, I'm marking my HttpContextBase
and Dictionary
members as volatile
The Tests
[TestClass]
public class ContextResolverTests
{
[TestClass]
public class TheCreateContextInstanceMethod
{
[TestMethod]
public void it_should_resolve_a_valid_instance()
{
ContextResolver sut = new ConcreteResolverBuilder().Initialize();
var res = sut.GetCurrentContext<PubsContext>();
res.Should().NotBeNull().And.BeOfType<PubsContext>();
}
[TestMethod]
public void it_should_return_the_same_instance_even_when_calling_it_from_different_threads()
{
ContextResolver sut = new ConcreteResolverBuilder().Initialize();
var firstResolvedContext = sut.GetCurrentContext<PubsContext>();
var numberOfConcurrentProcess = 10000;
var contextResolverInstances = new BlockingCollection<DbContext>();
var tasks = new Task[numberOfConcurrentProcess];
for (int i = 0; i < numberOfConcurrentProcess; i++)
{
tasks[i] = Task.Factory.StartNew(() =>
{
var context = new ConcreteResolverBuilder().Initialize().Build().GetCurrentContext<PubsContext>();
contextResolverInstances.Add(context);
}, TaskCreationOptions.LongRunning);
}
Task.WaitAll(tasks);
contextResolverInstances
.Should()
.NotBeNull()
.And
.NotBeEmpty()
.And
.HaveCount(numberOfConcurrentProcess)
.And
.ContainItemsAssignableTo<DbContext>()
.And
.NotContainNulls();
var firstResolvedContextHashCode = (firstResolvedContext as object).GetHashCode();
firstResolvedContextHashCode.Should().NotBe(default(int));
foreach (var item in contextResolverInstances)
{
var itemHashCode = (item as object).GetHashCode();
itemHashCode.Should().NotBe(default(int));
itemHashCode.Should().Be(firstResolvedContextHashCode);
item.Should().Be(firstResolvedContext);
var areReferenceEquals = ReferenceEquals(item, firstResolvedContext);
areReferenceEquals.Should().BeTrue();
}
}
}
}
The first test is really simple, it just ensures that we can create a context
The second test is more interesting, this is where I’m actually testing the concurrency of my implementation.
I keep track of the first context created to compare all subsequent contexts with this one.
var firstResolvedContext = sut.GetCurrentContext<PubsContext>();
I set the number of concurrent operations
var numberOfConcurrentProcess = 10000;
Now I create a list to store all the created contexts. Note that I’m using the
BlockingCollection
object which is thread-safe
var contextResolverInstances = new BlockingCollection<DbContext>();
I’m storing all the parallel tasks in an array
var tasks = new Task[numberOfConcurrentProcess];
I create and start all the tasks. On each task, I’m creating a new instance of my SUT (my concrete implementation of:
IContextResolver
) and adding tocontextResolverInstances
the result ofIContextResolver.GetCurrentContext
. Also note that I'm using:TaskCreationOptions.LongRunning
to force the TPL to run my tasks in background threads
for (int i = 0; i < numberOfConcurrentProcess; i++) { tasks[i] = Task.Factory.StartNew(() => { var context = new ConcreteResolverBuilder().Initialize().Build().GetCurrentContext<PubsContext>(); contextResolverInstances.Add(context); }, TaskCreationOptions.LongRunning); }
I wait for all the tasks to complete by calling:
Task.WaitAll(tasks);
The rest of the code are assertions, to confirm that just one instance was created among all threads
In the next screenshot you can see how the tasks are being executed on different threads
excelente post picoro jeje saludos
ReplyDeleteGracias
DeleteSaludos
This comment has been removed by the author.
ReplyDelete