Will my Unit of Work implementation cause memory leak? - entity-framework

I implemented my repository pattern and unit of work in the following way:
How to create a simple data access layer
How to create unit of work
and I was asked if create multiple instance of my database (which is the unit of work) will cause memory leak? and what are the downside of such implementation? or is the implementation done right?
Thanks.

What you should worry about is the creation of the DbContext as this is expensive.
I typically use one DbContext per request instead of creating multiple DbContext instances everywhere.
I store it in the HttpContext object and dispose of it at the end of the request.
You can find more info on it here
Find below my modified implementation. (original was from the link above and used ObjectContext).
My context class
using System.Data.Entity;
using System.Data.Objects;
using System.Web;
using Fot.Admin.Models;
namespace Context
{
public static class ContextManager
{
internal const string DB = "MY_DB_CONTEXT";
/// <summary>
/// Get an instance that lives for the life time of the request per user and automatically disposes.
/// </summary>
/// <returns>Model</returns>
public static T AsSingleton<T>() where T : DbContext, new()
{
HttpContext.Current.Items[DB] = (T)HttpContext.Current.Items[DB] ?? new T();
return (T)HttpContext.Current.Items[DB];
}
}
}
My Context Module
using System;
using System.Data.Entity;
using System.Data.Objects;
using System.Web;
namespace Context
{
/// <summary>
/// Entity Module used to control an Entities DB Context over the lifetime of a request per user.
/// </summary>
public class ContextModule : IHttpModule
{
private const string DB = ContextManager.DB;
void context_EndRequest(object sender, EventArgs e)
{
Dispose();
}
#region IHttpModule Members
public void Dispose()
{
if(HttpContext.Current != null)
{
if (HttpContext.Current.Items[DB] != null)
{
var entitiesContext = (DbContext) HttpContext.Current.Items[DB];
entitiesContext.Dispose();
HttpContext.Current.Items.Remove(DB);
}
}
}
public void Init(HttpApplication context)
{
context.EndRequest += new EventHandler(context_EndRequest);
}
#endregion
}
}
In my web.config under <httpModules> i add this below.
<add name="ContextModule" type="Context.ContextModule" />
This ensures that the end_Request is called after every request so you can dispose the context properly.
When you need the DbContext usage is as below.
var Context = ContextManager.AsSingleton<MyDBContext>();

Related

Disposal and injecting DbContexts with .NET Core

I know that one way to use a context is via the using statement.
I use it like so within my controllers
[ApiController]
public class MyController : ControllerBase
{
[HttpPost]
public ActionResult PostActionHere(ActionRequestClass request)
{
using (var context = new MyEntityFrameworkContext())
{
....
// use context here
context.SaveChanges()
....
}
}
}
I would like to start injecting it into my controller. Mainly because I think it is easier to read and is more uniform with .NET Core dependency injection.
[ApiController]
public class MyController : ControllerBase
{
private MyEntityFrameworkContext _myDb;
public MyController(MyEntityFrameworkContext myDb)
{
_myDb = myDb;
}
[HttpPost]
public ActionResult PostActionHere(ActionRequestClass request)
{
....
// use context here
_myDb.SaveChanges()
....
}
}
Within my startup.cs:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MyEntityFrameworkContext >(options =>
options.UseSqlServer(Configuration.GetConnectionString("MyEntityFrameworkDatabase")));
}
What I am worried about is that injecting it I lose the disposal properties that come with the using statement. Is that true? Feel free to suggest alternate approaches.
injecting it I lose the disposal properties that come with the using statement. Is that true?
No:
The AddDbContext extension method registers DbContext types with a
scoped lifetime by default.
Configuring a DbContext
And when the scope (here the HttpRequest) ends, the Scoped Lifetime object will be Disposed.

Unity with HierarchicalLifetimeManager. Do I need a 'Using' Statement with Entity Framework

I am using Unity and Entity Framework in a Web API 2 application. I register types with the HierarchicalLifetimeManager.
var container = new UnityContainer();
container.RegisterType<IService, Service>(new HierarchicalLifetimeManager());
Do I need to wrap all my dbContext calls in a using statement like this? Is this needed? I thought Unity will dispose the context for me.
using (var client = new dbContext())
{
var result = client.Customers.toList();
}
Or can I just use dbContext without the using statement?
Do I need to wrap all my dbContext calls in a using statement like
this?
I would say that it depends on how you use your context. Take this example:
public class Service : IService, IDisposable
{
private DbContext _context;
public Service(DbContext context)
{
_context = context;
}
public object Get(int id)
{
return _context.Customers.Find(id);
}
public object Update(object obj)
{
// Code for updating
}
public void Dispose()
{
_context.Dispose();
}
}
If you register Service with HierarchicalLifetimeManager the context will practially never be disposed, since nothing will dispose the service and thus never dispose the context. However, the example above should work fine with TransientLifetimeManager.
From MSDN:
HierarchicalLifetimeManager. For this lifetime manager, as for the
ContainerControlledLifetimeManager, Unity returns the same instance of
the registered type or object each time you call the Resolve or
ResolveAll method or when the dependency mechanism injects instances
into other classes.
If you instead dispose it in each method, then it will be correctly disposed regardless of what lifetimemanager you use. Also, the consumer of IService doesn't need to care about disposing IService.
public class Service : IService
{
public object Get(int id)
{
using (var context = new DbContext())
{
return context.Customers.Find(id);
}
}
public object Update(object obj)
{
// Code for updating
}
}
Also, consider what happens if your Service was Transient, and is injected in a manager that is ContainerController. Since the manager never is disposed, neither is the service. The manager will keep the same instance of the service throughout the lifetime of the container. Therefore I personally suggest that you keep the disposing of the context outside of the containers control. It can work very well with the help of lifetimemanagers if you make sure that you have a chain of disposings. There are several posts here and on codereview that show examples of UoW with Unity to dispose the context.

Disposing of object context when implementing a repository (DAL) and Mocking DAL for TDD

I am running into an issue where I can't figure out how to properly dispose of my object context I am creating every time I instantiate a new object.
public class OrderBLL{
var _iOrderLineDal;
public OrderBLL(){
_iOderLineDal = new OrderLineDal(new entityOBject(dbconnectionstring);
}
public OrderBLL(iOrderLineDal mockOrderLineDal){
_iOrderLineDal = mockOrderLineDal;
}
}
So the problem is, that every 30 seconds my service creates a new instance of the OrderBLL and then runs a method to see if there are any new orders in the Data base.
So every 30 seconds I create a new entityObject that is not being disposed of. the old implementation of the code was written using the using statement.
public bool HasNewOrders(){
using(var entityObject = new entityObject(dbconnectionString)){
var newOrders = entityObject.GetNewOrders();
}
//some logic
}
The problem with using this using statement is I cannot mock out the entityObject and easily write unit tests on any methods inside this OrderBLL class.
I tried disposing of it with a dispose method inside the OrderLineDal and once i got the data called dispose. That worked well the first iteration but the following iterations, the next 30 seconds, it would say that the entityObject was disposed of and cannot be used. (doesn't make sense to me, since I am creating a new one every time?)
Is there a way I can implement this repository pattern and still dispose of all the new entityObjects so I can mock the DAL out for unit testing?
I am working with EF 4. and it was not set up Code First, so I do not have POCO.
Ideally you would want to create your context outside of your OrderBLL (search google for Repository pattern).
public class OrderRepository : IOrderRepository, IDisposable
{
private readonly IOrderDBContext _dbContext;
// poor mans dependency injection
public OrderRepository() : this(new OrderDbContext("YourConnectionString")
{}
public OrderRepository(IOrderDBContext dbContext)
{
if (dbContext == null) throw new ArgumentNullException("dbContext");
_dbContext = dbContext;
}
public bool GetNewOrders(){
return _dbContext.Orders.Where(o => o.IsNew==true);
}
public void Dispose()
{
if (_dbContext != null) _dbContext.dispose();
}
}
public class OrderBLL : IOrderBLL
{
private readonly IOrderRepository _repository;
public OrderRepository(IOrderRepository repository)
{
if (repository == null) throw new ArgumentNullException("dbContext");
_repository = repository;
}
public bool HasNewOrders(){
var newOrders = _repository.GetNewOrders();
if (newOrders==null) return false;
return newOrders.Count() > 0;
}
}
[Test]
public void HasNewOrders_GivenNoNNewOrdersRetunedFromRepo_ReturnsFalse()
{
// test using nunit and nsubstitute
// Arrange
var repository = Substitue.For<IOrderRepository>();
var emptyOrderList = new List<Order>();
repository.GetNewOrders().Returns();
var orderBLL = new OrderBLL(repository);
// Act
var result = orderBLL.HasNewOrders();
// Assert
Assert.Equals(false, result);
}
Now you can inject your context into this class and easily test your business logic. Eventually you will need to create your dbContext and should also always expose this. I would suggest having a look at a DI container like Castle Windsor to manage the life of your objects, although in a service you may just want to manually create and dispose your context as close to the code entry point as possible (e.g. in the main method)

How to log queries using Entity Framework 7?

I am using Entity Framework 7 on the nightly build channel (right now I'm using version EntityFramework.7.0.0-beta2-11524) and I'm trying to log the queries that EF generates just out of curiosity.
I'm writing a simple console program, I tried using the same logging technic that EF6 uses, but DbContext.Database.Logis not available on Entity Framework 7. Is there a way to log or just take a peek at the SQL generated by EF7?
For those using EF7 none of the above worked for me. But this is how i got it working. (from #avi cherry's comment)
In your Startup.cs you proably have a Configure method with a bunch of configurations in it. It should look like below (in addition to your stuff).
public void Configure(IApplicationBuilder app, ILoggerFactory loggerFactory)
{
//this is the magic line
loggerFactory.AddDebug(LogLevel.Debug); // formerly LogLevel.Verbose
//your other stuff
}
You can log to the console using this code, I am sure it will be wrapped in a simpler api later:
using System;
using Microsoft.Data.Entity.Infrastructure;
using Microsoft.Data.Entity.Utilities;
using Microsoft.Framework.Logging;
public static class SqlCeDbContextExtensions
{
public static void LogToConsole(this DbContext context)
{
var loggerFactory = ((IAccessor<IServiceProvider>)context).GetService<ILoggerFactory>();
loggerFactory.AddProvider(new DbLoggerProvider());
}
}
And the DbLoggerProvider is implemented here: https://github.com/ErikEJ/EntityFramework7.SqlServerCompact/tree/master/src/Provider40/Extensions/Logging
If you are using MS SQL Server, one way I have used in the past is to make use of the SQL Server Profiler and capture all interaction with the SQL Server, this captures the exact SQL submitted and can be cut n pasted into the SQL Server Management Studio for further review/analysis.
I know this does not directly answer your question on Entity Framework, but I have found this generic approach very useful for any language/tools.
One tip is in the Trace Properties when setting up a new trace, I have found it useful to adjust the default selection of events in the Events Selection tab. Mostly I turn off the Audit Login/Logout unless specifically tracking such an issue.
I struggled with all the above answers as the EF bits kept changing, so the code wouldn't compile. As of today (19Feb2016) with EF7.0.0-rc1-final (Prerelease) and SQLite, here's what works for me:
From the EF7 documentation:
using System;
using System.IO;
using Microsoft.Extensions.Logging;
namespace EFLogging
{
public class EFLoggerProvider : ILoggerProvider
{
public ILogger CreateLogger(string categoryName)
{
return new EFLogger();
}
public void Dispose()
{
// N/A
}
private class EFLogger : ILogger
{
public IDisposable BeginScopeImpl(object state)
{
return null;
}
public bool IsEnabled(LogLevel logLevel)
{
return true;
}
public void Log(LogLevel logLevel, int eventId, object state, Exception exception, Func<object, Exception, string> formatter)
{
File.AppendAllText(#".\EF.LOG", formatter(state, exception));
Console.WriteLine(formatter(state, exception));
}
}
}
}
Using some ideas above and the EF7 Docs:
using System;
using Microsoft.Data.Entity;
using Microsoft.Data.Entity.Infrastructure;
using Microsoft.Extensions.DependencyInjection; // Add this to EF7 docs code
using Microsoft.Extensions.Logging;
namespace DataAccessLayer
{
public static class DbContextExtensions
{
public static void LogToConsole(this DbContext context)
{
var serviceProvider = context.GetInfrastructure<IServiceProvider>();
var loggerFactory = serviceProvider.GetService<ILoggerFactory>();
loggerFactory.AddProvider(new EFLoggerProvider(logLevel));
}
}
}
EDIT: #jnm2 pointed out if you add "using Microsoft.Extensions.DependencyInjection", the EF7 docs ARE correct. Thanks!
And finally, in my App.OnStartup method:
using (var db = new MyDbContext())
{
db.LogToConsole();
}
This code will create a log file and also output logging info to the Visual Studio output window. I hope this helps -- I'm sure in a few weeks, the bits will change again.
With the latest version of EF7-beta8, Anthony's answer need a little tweaking. Here's what I did to get it to work.
internal static class DbContextExtensions
{
public static void LogToConsole(this DbContext context)
{
var loggerFactory = context.GetService<ILoggerFactory>();
loggerFactory.AddConsole(LogLevel.Verbose);
}
}
I think I figured this out. With the current EF7 bits, ILoggerFactory is registered with the dependency injection container which EF is using. You can get a reference to the container, which is an IServiceProvider, via the ScopedServiceProvider property of DbContext when it is cast to IDbContextServices. From there, you can get the ILoggerFactory and configure it using the AddToConsole extension method from the Microsoft.Framework.Logging.Console NuGet package.
public static void LogToConsole(this DbContext context)
{
// IServiceProvider represents registered DI container
IServiceProvider contextServices = ((IDbContextServices)context).ScopedServiceProvider;
// Get the registered ILoggerFactory from the DI container
var loggerFactory = contextServices.GetRequiredService<ILoggerFactory>();
// Add a logging provider with a console trace listener
loggerFactory.AddConsole(LogLevel.Verbose);
}
Here is a gist I created for this snippet: https://gist.github.com/tonysneed/4cac4f4dae2b22e45ec4
This worked for me with EF7 rc2-16485:
"EntityFramework.MicrosoftSqlServer": "7.0.0-rc2-16485",
"Microsoft.Extensions.Logging.Console": "1.0.0-rc2-15888",
public static class DbContextExtensions
{
public static void LogToConsole(this DbContext context)
{
var contextServices = ((IInfrastructure<IServiceProvider>) context).Instance;
var loggerFactory = contextServices.GetRequiredService<ILoggerFactory>();
loggerFactory.AddConsole(LogLevel.Verbose);
}
}
As an alternative to the above answers, I found this answer by far the easiest solution for me to reason about:
private readonly ILoggerFactory loggerFactory;
// Using dependency injection
public FooContext(ILoggerFactory loggerFactor) {
this.loggerFactory = loggerFactory;
}
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) {
optionsBuilder.UseLoggerFactory(loggerFactory); // Register logger in context
}
With ASP.NET Core 2.0 you get SQL logging automatically. No need to do anything extra.
For those who just want SQL queries to be logged (using Entity Framework Core with .NET Core 2.0 or above), use the following code in your DbContext class:
public static readonly LoggerFactory MyLoggerFactory
= new LoggerFactory(new[]
{
new ConsoleLoggerProvider((category, level)
=> category == DbLoggerCategory.Database.Command.Name
&& level == LogLevel.Information, true)
});
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseLoggerFactory(MyLoggerFactory) // Warning: Do not create a new ILoggerFactory instance each time
.UseSqlServer(
#"Server=(localdb)\mssqllocaldb;Database=EFLogging;Trusted_Connection=True;ConnectRetryCount=0");
Reference: https://learn.microsoft.com/en-us/ef/core/miscellaneous/logging

Convert Library from ObjectContext to DbContext

I have a library (based on code found in an old blog post) that allows me to very easily wrap a façade around my data access using Entity Framework. It uses ObjectContext and has performed well enough for my purposes.
But now, we are excitedly investigating code first using DbContext and of course would like to reuse / adapt as much of our existing effort as possible.
Everything went ok naively converting our Facade enabling library with IObjectContextAdapter until we tried to utilise our facade, when the following error was received:
The type 'Employee' cannot be used as type parameter 'TEntity' in the generic type or method 'DbContextManagement.FacadeBase'. There is no implicit reference conversion from 'Employee' to 'System.Data.Objects.DataClasses.EntityObject'
MSDN says:
EntityObject derived types are not supported by the DbContext API, to use these entity types you must use the ObjectContext API.
That's fine, but how would I then go ahead completing my refactor to bypass this inability?
Here's some code (line breaks introduced):
FacadeBase.cs
namespace DbContextManagement
{
using System;
using System.Collections;
using System.Configuration;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Data.Metadata.Edm;
using System.Data.Objects.DataClasses;
using System.Linq;
using System.Reflection;
public abstract class FacadeBase<TDbContext, TEntity>
where TDbContext : DbContext, new()
where TEntity : EntityObject
{
protected TDbContext DbContext
{
get
{
if (DbContextManager == null)
{
this.InstantiateDbContextManager();
}
return DbContextManager.GetDbContext<TDbContext>();
}
}
private DbContextManager DbContextManager { get; set; }
public virtual void Add(TEntity newObject)
{
var context = ((IObjectContextAdapter)this.DbContext).ObjectContext;
string entitySetName;
if (newObject.EntityKey != null)
{
entitySetName = newObject.EntityKey.EntitySetName;
}
else
{
string entityTypeName = newObject.GetType().Name;
var container = context.MetadataWorkspace.GetEntityContainer(
context.DefaultContainerName,
DataSpace.CSpace);
entitySetName = (from meta in container.BaseEntitySets
where meta.ElementType.Name ==
entityTypeName
select meta.Name).First();
}
context.AddObject(entitySetName, newObject);
}
public virtual void Delete(TEntity obsoleteObject)
{
var context = ((IObjectContextAdapter)this.DbContext).ObjectContext;
context.DeleteObject(obsoleteObject);
}
private void InstantiateDbContextManager()
{
var objectContextManagerConfiguration =
ConfigurationManager.GetSection("DbContext") as Hashtable;
if (objectContextManagerConfiguration != null &&
objectContextManagerConfiguration.ContainsKey("managerType"))
{
var managerTypeName =
objectContextManagerConfiguration["managerType"] as string;
if (string.IsNullOrEmpty(managerTypeName))
{
throw new ConfigurationErrorsException(
"The managerType attribute is empty.");
}
managerTypeName = managerTypeName.Trim().ToLower();
try
{
var frameworkAssembly =
Assembly.GetAssembly(typeof(DbContextManager));
var managerType =
frameworkAssembly.GetType(managerTypeName, true, true);
this.DbContextManager =
Activator.CreateInstance(managerType) as DbContextManager;
}
catch (Exception e)
{
throw new ConfigurationErrorsException(
"The managerType specified in the
configuration is not valid.", e);
}
}
else
{
throw new ConfigurationErrorsException(
"A Facade.DbContext tag or its managerType attribute
is missing in the configuration.");
}
}
}
}
EmployeeFacade.cs
namespace Facade
{
using System.Collections.Generic;
using System.Linq;
using DataModel;
using DataModel.Entities;
using DbContextManagement;
public sealed class EmployeeFacade : FacadeBase<FleetContext, Employee>
{
public Employee GetById(int? employeeId)
{
return employeeId == null
? null
: this.DbContext.Employees.FirstOrDefault(m => m.Id == employeeId);
}
}
}
Employee.cs
namespace DataModel.Entities
{
public class Employee
{
public int Id { get; set; }
public string Surname { get; set; }
public string Forename { get; set; }
public string EmployeeNumber { get; set; }
}
}
If your entities are derived from EntityObject and the code you want to reuse is dependent on EntityObject based entities then it is a show stopper. You cannot use DbContext API until your entities are POCOs (no EntityObject parent).
Btw. you can use code only mapping with ObjectContext (and POCOs) without ever using DbContext. You just need to:
Create EntityTypeConfiguration or ComplexTypeConfiguration based class for each your POCO entity / complex type describing the mapping
Use DbModelBuilder to collect your configurations and call Build to get DbModel instance
Call Compile on your DbModel instance to get DbCompiledModel
Cache compiled model for lifetime of your application
When you need a new ObjectContext instance call CreateObjectContext on compiled model
Just be aware that code mapping has much more limited feature set so not everything you currently have in EDMX will be possible to achieve through code mapping.
With a very large nod to the author of the original code in the link given above, here is what I ended up with. It passes basic scenarios but I haven't yet tried out deeper navigational and related queries. Even if there is an issue, I reckon it will be bug fix rather than show stop.
Here goes. The following is reusable, works with multiple contexts and means you can go to the database and get something back in 2 lines of actual code in the client.
DataModel (Class Library Project)
Your EF Code First project with DbContext POCOs, etc.
DbContextManagement (Class Library Project)
DbContextManager.cs
namespace DbContextManagement
{
using System.Data.Entity;
/// <summary>
/// Abstract base class for all other DbContextManager classes.
/// </summary>
public abstract class DbContextManager
{
/// <summary>
/// Returns a reference to an DbContext instance.
/// </summary>
/// <typeparam name="TDbContext">The type of the db context.</typeparam>
/// <returns>The current DbContext</returns>
public abstract TDbContext GetDbContext<TDbContext>()
where TDbContext : DbContext, new();
}
}
DbContextScope.cs
namespace DbContextManagement
{
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data.Entity;
using System.Data.Entity.Infrastructure;
using System.Linq;
using System.Threading;
/// <summary>
/// Defines a scope wherein only one DbContext instance is created, and shared by all of those who use it.
/// </summary>
/// <remarks>Instances of this class are supposed to be used in a using() statement.</remarks>
public class DbContextScope : IDisposable
{
/// <summary>
/// List of current DbContexts (supports multiple contexts).
/// </summary>
private readonly List<DbContext> contextList;
/// <summary>
/// DbContext scope definitiion.
/// </summary>
[ThreadStatic]
private static DbContextScope currentScope;
/// <summary>
/// Holds a value indicating whether the context is disposed or not.
/// </summary>
private bool isDisposed;
/// <summary>
/// Initializes a new instance of the <see cref="DbContextScope"/> class.
/// </summary>
/// <param name="saveAllChangesAtEndOfScope">if set to <c>true</c> [save all changes at end of scope].</param>
protected DbContextScope(bool saveAllChangesAtEndOfScope)
{
if (currentScope != null && !currentScope.isDisposed)
{
throw new InvalidOperationException("DbContextScope instances cannot be nested.");
}
this.SaveAllChangesAtEndOfScope = saveAllChangesAtEndOfScope;
this.contextList = new List<DbContext>();
this.isDisposed = false;
Thread.BeginThreadAffinity();
currentScope = this;
}
/// <summary>
/// Gets or sets a value indicating whether to automatically save all object changes at end of the scope.
/// </summary>
/// <value><c>true</c> if [save all changes at end of scope]; otherwise, <c>false</c>.</value>
private bool SaveAllChangesAtEndOfScope { get; set; }
/// <summary>
/// Save all object changes to the underlying datastore.
/// </summary>
public void SaveAllChanges()
{
var transactions = new List<DbTransaction>();
foreach (var context in this.contextList
.Select(dbcontext => ((IObjectContextAdapter)dbcontext)
.ObjectContext))
{
context.Connection.Open();
var databaseTransaction = context.Connection.BeginTransaction();
transactions.Add(databaseTransaction);
try
{
context.SaveChanges();
}
catch
{
/* Rollback & dispose all transactions: */
foreach (var transaction in transactions)
{
try
{
transaction.Rollback();
}
catch
{
// "Empty general catch clause suppresses any errors."
// Haven't quite figured out what to do here yet.
}
finally
{
databaseTransaction.Dispose();
}
}
transactions.Clear();
throw;
}
}
try
{
/* Commit all complete transactions: */
foreach (var completeTransaction in transactions)
{
completeTransaction.Commit();
}
}
finally
{
/* Dispose all transactions: */
foreach (var transaction in transactions)
{
transaction.Dispose();
}
transactions.Clear();
/* Close all open connections: */
foreach (var context in this.contextList
.Select(dbcontext => ((IObjectContextAdapter)dbcontext).ObjectContext)
.Where(context => context.Connection.State != System.Data.ConnectionState.Closed))
{
context.Connection.Close();
}
}
}
/// <summary>
/// Disposes the DbContext.
/// </summary>
public void Dispose()
{
// Monitor for possible future bugfix.
// CA1063 : Microsoft.Design : Provide an overridable implementation of Dispose(bool)
// on 'DbContextScope' or mark the type as sealed. A call to Dispose(false) should
// only clean up native resources. A call to Dispose(true) should clean up both managed
// and native resources.
if (this.isDisposed)
{
return;
}
// Monitor for possible future bugfix.
// CA1063 : Microsoft.Design : Modify 'DbContextScope.Dispose()' so that it calls
// Dispose(true), then calls GC.SuppressFinalize on the current object instance
// ('this' or 'Me' in Visual Basic), and then returns.
currentScope = null;
Thread.EndThreadAffinity();
try
{
if (this.SaveAllChangesAtEndOfScope && this.contextList.Count > 0)
{
this.SaveAllChanges();
}
}
finally
{
foreach (var context in this.contextList)
{
try
{
context.Dispose();
}
catch (ObjectDisposedException)
{
// Monitor for possible future bugfix.
// CA2202 : Microsoft.Usage : Object 'databaseTransaction' can be disposed
// more than once in method 'DbContextScope.SaveAllChanges()'.
// To avoid generating a System.ObjectDisposedException you should not call
// Dispose more than one time on an object.
}
}
this.isDisposed = true;
}
}
/// <summary>
/// Returns a reference to a DbContext of a specific type that is - or will be -
/// created for the current scope. If no scope currently exists, null is returned.
/// </summary>
/// <typeparam name="TDbContext">The type of the db context.</typeparam>
/// <returns>The current DbContext</returns>
protected internal static TDbContext GetCurrentDbContext<TDbContext>()
where TDbContext : DbContext, new()
{
if (currentScope == null)
{
return null;
}
var contextOfType = currentScope.contextList
.OfType<TDbContext>()
.FirstOrDefault();
if (contextOfType == null)
{
contextOfType = new TDbContext();
currentScope.contextList.Add(contextOfType);
}
return contextOfType;
}
}
}
FacadeBase.cs
namespace DbContextManagement
{
using System;
using System.Collections;
using System.Collections.Generic;
using System.Configuration;
using System.Data.Entity;
using System.Reflection;
/// <summary>
/// Generic base class for all other Facade classes.
/// </summary>
/// <typeparam name="TDbContext">The type of the db context.</typeparam>
/// <typeparam name="TEntity">The type of the entity.</typeparam>
/// <typeparam name="TEntityKey">The type of the entity key.</typeparam>
/// <remarks>Not sure the handling of TEntityKey is something I've worked with properly.</remarks>
public abstract class FacadeBase<TDbContext, TEntity, TEntityKey>
where TDbContext : DbContext, new()
where TEntity : class
{
/// <summary>
/// Gets the db context.
/// </summary>
public TDbContext DbContext
{
get
{
if (DbContextManager == null)
{
this.InstantiateDbContextManager();
}
return DbContextManager != null
? DbContextManager.GetDbContext<TDbContext>()
: null;
}
}
/// <summary>
/// Gets or sets the DbContextManager.
/// </summary>
/// <value>The DbContextManager.</value>
private DbContextManager DbContextManager { get; set; }
/// <summary>
/// Adds a new entity object to the context.
/// </summary>
/// <param name="newObject">A new object.</param>
public virtual void Add(TEntity newObject)
{
this.DbContext.Set<TEntity>().Add(newObject);
}
/// <summary>
/// Deletes an entity object.
/// </summary>
/// <param name="obsoleteObject">An obsolete object.</param>
public virtual void Delete(TEntity obsoleteObject)
{
this.DbContext.Set<TEntity>().Remove(obsoleteObject);
}
/// <summary>
/// Gets all entities for the given type.
/// </summary>
/// <returns>DbContext Set of TEntity.</returns>
public virtual IEnumerable<TEntity> GetAll()
{
return this.DbContext.Set<TEntity>();
}
/// <summary>
/// Gets the entity by the specified entity key.
/// </summary>
/// <param name="entityKey">The entity key.</param>
/// <returns>Entity matching specified entity key or null if not found.</returns>
public virtual TEntity GetByKey(TEntityKey entityKey)
{
return this.DbContext.Set<TEntity>().Find(entityKey);
}
/// <summary>
/// Deletes the entity for the specified entity key.
/// </summary>
/// <param name="entityKey">The entity key.</param>
public virtual void DeleteByKey(TEntityKey entityKey)
{
var entity = this.DbContext.Set<TEntity>().Find(entityKey);
if (entity != null)
{
this.DbContext.Set<TEntity>().Remove(entity);
}
}
/// <summary>
/// Instantiates a new DbContextManager based on application configuration settings.
/// </summary>
private void InstantiateDbContextManager()
{
/* Retrieve DbContextManager configuration settings: */
var contextManagerConfiguration = ConfigurationManager.GetSection("DbContext") as Hashtable;
if (contextManagerConfiguration == null)
{
throw new ConfigurationErrorsException("A Facade.DbContext tag or its managerType attribute is missing in the configuration.");
}
if (!contextManagerConfiguration.ContainsKey("managerType"))
{
throw new ConfigurationErrorsException("dbManagerConfiguration does not contain key 'managerType'.");
}
var managerTypeName = contextManagerConfiguration["managerType"] as string;
if (string.IsNullOrEmpty(managerTypeName))
{
throw new ConfigurationErrorsException("The managerType attribute is empty.");
}
managerTypeName = managerTypeName.Trim().ToUpperInvariant();
try
{
/* Try to create a type based on it's name: */
var frameworkAssembly = Assembly.GetAssembly(typeof(DbContextManager));
var managerType = frameworkAssembly.GetType(managerTypeName, true, true);
/* Try to create a new instance of the specified DbContextManager type: */
this.DbContextManager = Activator.CreateInstance(managerType) as DbContextManager;
}
catch (Exception e)
{
throw new ConfigurationErrorsException("The managerType specified in the configuration is not valid.", e);
}
}
}
}
ScopedDbContextManager.cs
namespace DbContextManagement
{
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
/// <summary>
/// Manages multiple db contexts.
/// </summary>
public sealed class ScopedDbContextManager : DbContextManager
{
/// <summary>
/// List of Object Contexts.
/// </summary>
private List<DbContext> contextList;
/// <summary>
/// Returns the DbContext instance that belongs to the current DbContextScope.
/// If currently no DbContextScope exists, a local instance of an DbContext
/// class is returned.
/// </summary>
/// <typeparam name="TDbContext">The type of the db context.</typeparam>
/// <returns>Current scoped DbContext.</returns>
public override TDbContext GetDbContext<TDbContext>()
{
var currentDbContext = DbContextScope.GetCurrentDbContext<TDbContext>();
if (currentDbContext != null)
{
return currentDbContext;
}
if (this.contextList == null)
{
this.contextList = new List<DbContext>();
}
currentDbContext = this.contextList.OfType<TDbContext>().FirstOrDefault();
if (currentDbContext == null)
{
currentDbContext = new TDbContext();
this.contextList.Add(currentDbContext);
}
return currentDbContext;
}
}
}
UnitOfWorkScope.cs
namespace DbContextManagement
{
/// <summary>
/// Defines a scope for a business transaction. At the end of the scope all object changes can be persisted to the underlying datastore.
/// </summary>
/// <remarks>Instances of this class are supposed to be used in a using() statement.</remarks>
public sealed class UnitOfWorkScope : DbContextScope
{
/// <summary>
/// Initializes a new instance of the <see cref="UnitOfWorkScope"/> class.
/// </summary>
/// <param name="saveAllChangesAtEndOfScope">if set to <c>true</c> [save all changes at end of scope].</param>
public UnitOfWorkScope(bool saveAllChangesAtEndOfScope)
: base(saveAllChangesAtEndOfScope)
{
}
}
}
Facade (Class Library Project)
YourEntityFacade.cs
namespace Facade
{
using System.Collections.Generic;
using System.Linq;
using DataModel;
using DataModel.Entities;
using DbContextManagement;
public class YourEntityFacade : FacadeBase<YourDbContext, YourEntity, int>
{
public override IEnumerable<YourEntity> GetAll()
{
return base.GetAll()
.Distinct()
.ToList();
}
}
}
TestConsole (Console Application Project)
App.config
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="DbContext" type="System.Configuration.SingleTagSectionHandler" />
</configSections>
<DbContext managerType="DbContextManagement.ScopedDbContextManager" />
<connectionStrings>
<add
name="YourDbContext"
providerName="System.Data.SqlClient"
connectionString="Your connection string" />
</connectionStrings>
</configuration>
Program.cs
namespace TestConsole
{
using System;
using System.Collections.Generic;
using DataModel.Entities;
using DbContextManagement;
using Facade;
public static class Program
{
public static void Main()
{
TestGetAll();
Console.ReadLine();
}
private static void TestGetAll()
{
Console.WriteLine();
Console.WriteLine("Test GetAll()");
Console.WriteLine();
IEnumerable<YourEntity> yourEntities;
using (new UnitOfWorkScope(false))
{
yourEntities= new YourEntityFacade().GetAll();
}
if (yourEntities != null)
{
foreach (var yourEntity in yourEntities)
{
Console.WriteLine(
string.Format("{0}, {1}",
yourEntity.Id,
yourEntity.Name));
}
}
else
{
Console.WriteLine("GetAll() NULL");
}
}
}
}
So, usage is very simple and you can work with multiple contexts and facades within a scope. With this approach, the only code you are writing is custom queries. No more endless updating UnitOfWorks with repository references and authoring copy-cat repositories. I think it's great but be aware this is beta code and I'm sure it has a large hole somewhere that will need plugging :)
Thank you to all and in particular Ladislav for patience and help on this and many other related questions I ask. I hope this code is of interest. I contacted the author at the blog above but no reply yet and these days I think he's into NHibernate.
Richard