This is my architecture to EF4 using repository pattern and unit of work pattern with POCO. I believe I made some mistakes.
I have a solution with 5 projects:
MyApp.Common
MyApp.Data.EF4
MyApp.Domain.Company
MyApp.Domain.Transmission
MyApp.Web
In addition, I am using StructureMap
in this application in order to inject the data layer.
Now I will describe what each project contains - with source:
MyApp.Common
- contains common classes and interfaces
Repository interface:
public interface IRepository<T> where T : class
{
IQueryable<T> GetQuery(IEnumerable<Expression<Func<T, object>>> includes);
IPaged<T> GetQuery(IQueryable<T> query,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy, int pageNumber, int pageSize);
IPaged<T> GetQuery(IEnumerable<T> query,
Func<IEnumerable<T>, IOrderedEnumerable<T>> orderBy, int pageNumber, int pageSize);
IEnumerable<T> GetObjectStateManagerChanges();
void Insert(T entity);
void MarkModified(T entity);
void Delete(T entity);
void Attach(T entity);
void Detach(T entity);
T GetOriginalEntity(Func<T, bool> predicate);
}
Unit of work interface:
public interface IUnitOfWork : IDisposable
{
int Commit();
}
Unit of work factory interface:
public interface IUnitOfWorkFactory
{
IUnitOfWork Create();
}
Unit of work:
public static class UnitOfWork
{
private const string HTTPCONTEXTKEY = "MyApp.Common.HttpContext.Key";
private static IUnitOfWorkFactory _unitOfWorkFactory;
private static readonly Hashtable _threads = new Hashtable();
public static void Commit()
{
IUnitOfWork unitOfWork = GetUnitOfWork();
if (unitOfWork != null)
{
unitOfWork.Commit();
}
}
public static IUnitOfWork Current
{
get
{
IUnitOfWork unitOfWork = GetUnitOfWork();
if (unitOfWork == null)
{
_unitOfWorkFactory = ObjectFactory.GetInstance<IUnitOfWorkFactory>();
unitOfWork = _unitOfWorkFactory.Create();
SaveUnitOfWork(unitOfWork);
}
return unitOfWork;
}
}
public static void Dispose()
{
IUnitOfWork unitOfWork = GetUnitOfWork();
if (unitOfWork != null)
{
unitOfWork.Dispose();
}
}
private static IUnitOfWork GetUnitOfWork()
{
if (HttpContext.Current != null)
{
if (HttpContext.Current.Items.Contains(HTTPCONTEXTKEY))
{
return (IUnitOfWork)HttpContext.Current.Items[HTTPCONTEXTKEY];
}
return null;
}
else
{
Thread thread = Thread.CurrentThread;
if (string.IsNullOrEmpty(thread.Name))
{
thread.Name = Guid.NewGuid().ToString();
return null;
}
else
{
lock (_threads.SyncRoot)
{
return (IUnitOfWork)_threads[Thread.CurrentThread.Name];
}
}
}
}
private static void SaveUnitOfWork(IUnitOfWork unitOfWork)
{
if (HttpContext.Current != null)
{
HttpContext.Current.Items[HTTPCONTEXTKEY] = unitOfWork;
}
else
{
lock (_threads.SyncRoot)
{
_threads[Thread.CurrentThread.Name] = unitOfWork;
}
}
}
}
Base repository for all entities:
public abstract class BaseRepository<T> where T : class
{
protected IRepository<T> Repository { get; set; }
public IEnumerable<Expression<Func<T, object>>> Includes { private get; set; }
public BaseRepository()
{
Repository = ObjectFactory.GetInstance<IRepository<T>>();
Includes = null;
}
protected virtual IQueryable<T> GetQuery()
{
return Repository.GetQuery(Includes).AsEnumerable().AsQueryable();
}
protected virtual IQueryable<T> GetQuery(params Expression<Func<T, object>>[] includes)
{
return Repository.GetQuery(includes);
}
protected virtual IPaged<T> GetQuery(IEnumerable<T> query,
Func<IEnumerable<T>, IOrderedEnumerable<T>> orderBy, int pageNumber, int pageSize)
{
return Repository.GetQuery(query, orderBy, pageNumber, pageSize);
}
protected virtual IPaged<T> GetQuery(IQueryable<T> query,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy, int pageNumber, int pageSize)
{
return Repository.GetQuery(query, orderBy, pageNumber, pageSize);
}
protected virtual IEnumerable<T> GetObjectStateManagerChanges()
{
return Repository.GetObjectStateManagerChanges();
}
protected virtual void Insert(T entity)
{
Repository.Insert(entity);
}
protected virtual void MarkModified(T entity)
{
Repository.MarkModified(entity);
}
protected virtual void Delete(T entity)
{
Repository.Delete(entity);
}
protected virtual void Attach(T entity)
{
Repository.Attach(entity);
}
protected virtual void Detach(T entity)
{
Repository.Detach(entity);
}
protected virtual T GetOriginalEntity(Func<T, bool> predicate)
{
return Repository.GetOriginalEntity(predicate);
}
}
MyApp.Data.EF4 - Holds reference of MyApp.Common
and implements all interfaces using the EF way. It also includes the MyAppModel.edmx that holds models for all the entities in the system. MyAppModel.edmx has "Code Generation Strategy" set to Default. The entity container name is MyAppEntities
.
Repository implementation:
public class Repository<T> : IRepository<T> where T : class
{
ObjectContext _context;
IObjectSet<T> _objectSet;
protected ObjectContext Context
{
get
{
if (_context == null)
{
_context = GetCurrentUnitOfWork<EFUnitOfWork>().Context;
}
return _context;
}
}
protected IObjectSet<T> ObjectSet
{
get
{
if (_objectSet == null)
{
_objectSet = this.Context.CreateObjectSet<T>();
}
return _objectSet;
}
}
public TUnitOfWork GetCurrentUnitOfWork<TUnitOfWork>() where TUnitOfWork : IUnitOfWork
{
return (TUnitOfWork)UnitOfWork.Current;
}
public virtual IQueryable<T> GetQuery(IEnumerable<Expression<Func<T, object>>> includes)
{
return ObjectSet.IncludeMultiple(includes);
}
public virtual IPaged<T> GetQuery(IQueryable<T> query,
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy, int pageNumber, int pageSize)
{
if (orderBy != null)
{
query = orderBy(query);
}
IPaged<T> page = new Paged<T>(query, pageNumber, pageSize);
return page;
}
public virtual IPaged<T> GetQuery(IEnumerable<T> query,
Func<IEnumerable<T>, IOrderedEnumerable<T>> orderBy, int pageNumber, int pageSize)
{
if (orderBy != null)
{
query = orderBy(query);
}
IPaged<T> page = new Paged<T>(query, pageNumber, pageSize);
return page;
}
public virtual IEnumerable<T> GetObjectStateManagerChanges()
{
return this.Context.ObjectStateManager.
GetObjectStateEntries(EntityState.Added | EntityState.Modified).
Select(e => e.Entity).
OfType<T>();
}
public virtual void Insert(T entity)
{
this.ObjectSet.AddObject(entity);
}
public virtual void Delete(T entity)
{
this.ObjectSet.DeleteObject(entity);
}
public virtual void MarkModified(T entity)
{
this.Context.ObjectStateManager.ChangeObjectState(entity, EntityState.Modified);
}
public virtual void Attach(T entity)
{
ObjectStateEntry entry = null;
if (this.Context.ObjectStateManager.TryGetObjectStateEntry(entity, out entry) == false)
{
this.ObjectSet.Attach(entity);
}
}
public virtual void Detach(T entity)
{
ObjectStateEntry entry = null;
if (this.Context.ObjectStateManager.TryGetObjectStateEntry(entity, out entry) == true)
{
this.ObjectSet.Detach(entity);
}
}
public virtual T GetOriginalEntity(Func<T, bool> predicate)
{
T originalEntity = null;
EFUnitOfWorkFactory factory = new EFUnitOfWorkFactory();
using (EFUnitOfWork uow = (EFUnitOfWork)factory.Create())
{
originalEntity = uow.Context.CreateObjectSet<T>().Single(predicate);
}
return originalEntity;
}
}
Extension that used in the repository implementation:
public static class Extensions
{
public static IQueryable<T> IncludeMultiple<T>(this IQueryable<T> query,
IEnumerable<Expression<Func<T, object>>> includes)
where T : class
{
if (includes != null)
{
query = includes.Aggregate(query,
(current, include) => current.Include(include));
}
return query;
}
}
Implementation for IUnitOfWork
:
public class EFUnitOfWork : IUnitOfWork, IDisposable
{
public ObjectContext Context { get; private set; }
public int Id { get; private set; }
public EFUnitOfWork(ObjectContext context, int id)
{
Id = id;
Context = context;
Context.ContextOptions.LazyLoadingEnabled = false;
}
public int Commit()
{
return Context.SaveChanges();
}
public void Dispose()
{
if (Context != null)
{
Context.Dispose();
Context = null;
}
GC.SuppressFinalize(this);
}
}
Implementation of IUnitOfWorkFactory
:
public class EFUnitOfWorkFactory : IUnitOfWorkFactory
{
private static int Counter = 0;
private static Func<ObjectContext> _objectContextDelegate;
private static readonly Object _lockObject = new object();
public static void SetObjectContext(Func<ObjectContext> objectContextDelegate)
{
_objectContextDelegate = objectContextDelegate;
}
public IUnitOfWork Create()
{
ObjectContext context;
lock (_lockObject)
{
Counter++;
context = _objectContextDelegate();
}
return new EFUnitOfWork(context, Counter);
}
}
MyApp.Domain.Company
and MyApp.Domain.Transmission
- Contains POCO objects that should hold the query results and be the business logic of the solution. Each project reflects other and different need of the system. But, entities from MyApp.Domain.Company
have relationships in the DB and in the .edmx with MyApp.Domain.Transmission
entities.
For example: Vehicle
that exists in MyApp.Domain.Company
references by VehicleTransmission
that exists in MyApp.Domain.Transmission
. In addition, VehicleTransmission
holds reference of Vehicle
and therefore MyApp.Domain.Transmission
contains reference of MyApp.Domain.Company
. Both projects hold reference to MyApp.Common
. It is important to remember that T_TBL_VEHICLE
and T_TBL_VEHICLE_TRANSMISSION
(and the connections between them) represented in the same .edmx file. Each .cs file under those projects contains the POCO entity and a repository manager.
Vehicle.cs (from MyApp.Domain.Company
)
public class Vehicle
{
public virtual long Id { get; set; }
public virtual string VehicleNumber { get; set; }
public virtual DateTime? PurchaseDate { get; set; }
public virtual string InsuranceAgency { get; set; }
public virtual DateTime? InsuranceValidity { get; set; }
public virtual string Comments { get; set; }
public virtual IList<Worker> Workers { get; set; }
public string GetPhoneNumber()
{
string phoneNumber = null;
if (PhoneId.HasValue)
{
phoneNumber = Phone.PhoneNumber1;
if (string.IsNullOrEmpty(phoneNumber))
{
phoneNumber = Phone.PhoneNumber2;
}
}
return phoneNumber;
}
public string GetManufacturerName()
{
string name = null;
if (ManufacturerId.HasValue)
{
name = Manufacturer.Name;
}
return name;
}
}
public class VehicleRepository : BaseRepository<Vehicle>
{
private bool IsCounterExists(Guid companyId, long counter)
{
return GetQuery().
Where(x => x.CompanyId == companyId).
Where(x => x.IsDeleted == false).
AsEnumerable().
Any(x => x.Code.ToNullable<long>() == counter);
}
public void Attach(Vehicle entity)
{
base.Attach(entity);
}
public void MarkModified(Vehicle entity)
{
base.MarkModified(entity);
}
public void Delete(Vehicle entity)
{
base.Delete(entity);
}
public void Insert(Vehicle entity)
{
if (entity.VehicleNumber != null) { entity.VehicleNumber.Trim(); }
if (entity.VehicleNumber == null || entity.VehicleNumber == string.Empty)
{
throw new MissingFieldException(
CompanyExceptionMessage.MissingFieldException_Vehicle_Number.Message);
}
if (string.IsNullOrEmpty(entity.Code))
{
StaffCounterRepository rep = new StaffCounterRepository();
entity.Code = rep.GetAndAdvanceCounter(entity.CompanyId,
StaffCounterEnum.VEHICLE,
counter => IsCounterExists(entity.CompanyId, counter)).ToString();
}
int equals = GetQuery().
Where(x => x.CompanyId == entity.CompanyId).
Where(x => x.IsDeleted == false).
Where(x =>
(x.Code != null && x.Code == entity.Code) ||
(x.VehicleNumber == entity.VehicleNumber)
).Count();
if (equals > 0)
{
throw new ExistsNameOrCodeException(
CompanyExceptionMessage.ExistsNumberOrCodeException_Vehicle);
}
base.Insert(entity);
}
public Vehicle Get(Guid companyId, long vehicleId)
{
return GetQuery().Where(x => x.CompanyId == companyId).
Single(x => x.Id == vehicleId);
}
public IQueryable<Vehicle> Get(Guid companyId)
{
return GetQuery().
Where(x => x.CompanyId == companyId).
Where(x => x.IsDeleted == false).
OrderBy(x => x.VehicleNumber);
}
public Vehicle Get(Guid companyId, string code)
{
return GetQuery().
Where(x => x.CompanyId == companyId).
Where(x => x.IsDeleted == false).
FirstOrDefault(x => x.Code == code);
}
public IQueryable<Vehicle> Search(Guid companyId, string vehicleNumber)
{
var query = GetQuery().
Where(x => x.CompanyId == companyId).
Where(x => x.IsDeleted == false);
if (vehicleNumber != null)
{
query = query.Where(x => x.VehicleNumber.Contains(vehicleNumber));
}
return query.OrderBy(x => x.VehicleNumber);
}
public IEnumerable<Vehicle> Get(Guid companyId, bool? freeVehicles,
bool includeContractorVehicles)
{
IEnumerable<Vehicle> query = GetQuery().
Where(x => x.CompanyId == companyId).
Where(x => x.IsDeleted == false);
if (freeVehicles == true)
{
query = query.Where(x => x.Workers.Count == 0);
}
else if (freeVehicles == false)
{
query = query.Where(x => x.Workers.Count > 0);
}
query = query.AsEnumerable();
if (includeContractorVehicles == true)
{
WorkerRepository rep = new WorkerRepository();
IEnumerable<Vehicle> contractorsVehicles = rep.Get(companyId).
Where(x => x.ContractorVehicleNumber != null &&
x.ContractorVehicleNumber != string.Empty).
AsEnumerable().Select(x => new Vehicle()
{
VehicleNumber = x.ContractorVehicleNumber
});
query = query.Union(contractorsVehicles);
}
return query;
}
public IQueryable<Vehicle> Get(Guid companyId, VehicleStatusEnum? status,
string code, string vehicleNumber)
{
var query = GetQuery().
Where(x => x.CompanyId == companyId).
Where(x => x.IsDeleted == false);
if (status.HasValue)
{
long? statusId = VehicleStatusWrapper.GetId<VehicleStatusWrapper>(status.Value);
query = query.Where(x => x.StatusId == statusId);
}
if (!string.IsNullOrEmpty(code))
{
query = query.Where(x => x.Code.Contains(code));
}
if (!string.IsNullOrEmpty(vehicleNumber))
{
query = query.Where(x => x.VehicleNumber.Contains(vehicleNumber));
}
return query;
}
}
VehicleTransmission.cs (from MyApp.Domain.Transmission)
public class VehicleTransmission
{
public long? VehicleId { get; set; }
public string VehicleNumber { get; set; }
public long? SubcontractorId { get; set; }
public bool WorkerOnHold { get; set; }
public Vehicle Vehicle { get; set; }
public void SetVehicleId(long? vehicleId)
{
VehicleId = vehicleId;
}
//Possibility to set vehicle that is not exists on the db
public void SetVehicleNumber(string vehicleNumber)
{
VehicleId = null;
VehicleNumber = vehicleNumber;
}
}
public class VehicleTransmissionRepository : BaseRepository<VehicleTransmission>
{
public IQueryable<VehicleTransmission> Get()
{
return GetQuery();
}
}
MyApp.Web
- Contains the web application. Holds references to all other projects.
In the global.asax, in Application_Start
I configure StructureMap
:
ObjectFactory.Configure(x =>
{
x.For<IUnitOfWorkFactory>().Use<EFUnitOfWorkFactory>();
x.For(typeof(IRepository<>)).Use(typeof(Repository<>));
x.For(typeof(IEnumRepository<,>)).Use(typeof(EnumRepository<,>));
});
EFUnitOfWorkFactory.SetObjectContext(() => new MyAppEntities());
In addition, in Application_EndRequest I do:
UnitOfWork.Dispose();
I also have web services where there I use the repositories and call UnitOfWork.Commit()
when needed.
So far, this is my architecture. I really want to know if I made things right and where are my pitfalls.
larger
. \$\endgroup\$UnitOfWorkScope
to manage life cycle ofUnitOfWork
and take differentUnitOfWorkScope
such asWCFUnitOfWorkScope
,WebUnitOfWorkScope
. Their functionality are not applicable by dependency injection containers. \$\endgroup\$