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; }
}
}
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
DbContextScope.cs
FacadeBase.cs
ScopedDbContextManager.cs
UnitOfWorkScope.cs
Facade (Class Library Project)
YourEntityFacade.cs
TestConsole (Console Application Project)
App.config
Program.cs
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