Created
June 14, 2017 16:55
-
-
Save klmallory/ef5e8aef92bba91998926137d5a08850 to your computer and use it in GitHub Desktop.
Simple Entity Framework Repository Pattern With Nesting Units of Work
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Data.Entity; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using System.Patterns.Repository; | |
namespace System.Data.Entity.Repository | |
{ | |
public class EntityRepository<EntityType> : IRepository<EntityType> where EntityType : class | |
{ | |
public EntityRepository(string name, IServiceLocator locator) | |
{ | |
this.name = name; | |
this.locator = locator; | |
this.unitOfWorkStore = locator.Resolve<IUnitOfWorkStore>(name); | |
} | |
static readonly object syncRoot = new object(); | |
[ThreadStatic] | |
IUnitOfWork transientUow; | |
public IUnitOfWork TransientUow | |
{ | |
get | |
{ | |
lock (syncRoot) | |
if (this.transientUow == null) | |
this.transientUow = BeginUnitOfWork(); | |
return this.transientUow; | |
} | |
} | |
string name; | |
IServiceLocator locator; | |
IUnitOfWorkStore unitOfWorkStore; | |
public IUnitOfWork BeginUnsafeUnitOfWork() | |
{ | |
var uow = this.locator.Resolve<IUnitOfWork>(name + "_unsafe"); | |
unitOfWorkStore.ActiveUnitOfWork = uow; | |
return uow; | |
} | |
public IUnitOfWork BeginUnitOfWork() | |
{ | |
var uow = this.locator.Resolve<IUnitOfWork>(name + "_safe"); | |
unitOfWorkStore.ActiveUnitOfWork = uow; | |
return uow; | |
} | |
public IUnitOfWork BeginLockedUnitOfWork() | |
{ | |
var uow = this.locator.Resolve<IUnitOfWork>(name + "_locked"); | |
unitOfWorkStore.ActiveUnitOfWork = uow; | |
return uow; | |
} | |
public IQueryable<EntityType> Query() | |
{ | |
var uow = this.unitOfWorkStore.ActiveUnitOfWork; | |
if (uow != null) | |
{ | |
return uow.Get<EntityType>(); | |
} | |
else | |
{ | |
return TransientUow.Get<EntityType>(); | |
} | |
} | |
public void Integrate(EntityType entity) | |
{ | |
var uow = this.unitOfWorkStore.ActiveUnitOfWork; | |
if (uow == null) | |
throw new InvalidOperationException("No Active Unit Of Work to Integrate With"); | |
uow.Integrate<EntityType>(entity); | |
} | |
public void Add(EntityType entity) | |
{ | |
var uow = this.unitOfWorkStore.ActiveUnitOfWork; | |
if (uow == null) | |
throw new InvalidOperationException("No working context for Add."); | |
uow.Add(entity); | |
} | |
public void Add(List<EntityType> entities) | |
{ | |
var uow = this.unitOfWorkStore.ActiveUnitOfWork; | |
if (uow == null) | |
throw new InvalidOperationException("No working context for Add."); | |
uow.Add(entities); | |
} | |
public void Remove(EntityType entity) | |
{ | |
var uow = this.unitOfWorkStore.ActiveUnitOfWork; | |
if (uow == null) | |
throw new InvalidOperationException("No working context for Remove."); | |
uow.Remove(entity); | |
} | |
public void Remove(List<EntityType> entities) | |
{ | |
var uow = this.unitOfWorkStore.ActiveUnitOfWork; | |
if (uow == null) | |
throw new InvalidOperationException("No working context for Remove."); | |
uow.RemoveRange(entities); | |
} | |
public void Dispose() | |
{ | |
lock (syncRoot) | |
if (transientUow != null) | |
{ | |
transientUow.Dispose(); | |
transientUow = null; | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Data.Entity; | |
using System.Linq; | |
namespace Sysytem.Patterns.Repository | |
{ | |
public interface IRepository<EntityType> : IDisposable where EntityType : class | |
{ | |
void Add(List<EntityType> entities); | |
void Add(EntityType entity); | |
IUnitOfWork BeginLockedUnitOfWork(); | |
IUnitOfWork BeginUnitOfWork(); | |
IUnitOfWork BeginUnsafeUnitOfWork(); | |
IQueryable<EntityType> Query(); | |
void Remove(List<EntityType> entities); | |
void Remove(EntityType entity); | |
void Integrate(EntityType entity); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Data.Entity; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
namespace System.Patterns.Repository | |
{ | |
public interface IUnitOfWork : IDisposable | |
{ | |
Guid DataContextHandle { get; } | |
int ThreadId { get; } | |
bool ValidState { get; } | |
IQueryable<EntityType> Get<EntityType>() where EntityType : class; | |
IDisposable BeginTransaction(); | |
void AddRange<EntityType>(IEnumerable<EntityType> entities) where EntityType : class; | |
void Add<EntityType>(EntityType entity) where EntityType : class; | |
void Remove<EntityType>(EntityType entity) where EntityType : class; | |
void RemoveRange<EntityType>(IList<EntityType> entities) where EntityType : class; | |
void Integrate<EntityType>(EntityType entity) where EntityType :class; | |
EntityType Create<EntityType>() where EntityType : class; | |
void Commit(); | |
event UnitOfWorkCommitting UnitOfWorkCommitting; | |
event UnitOfWorkCanceling UnitOfWorkCanceling; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System.Collections.Generic; | |
using System.Threading; | |
using System; | |
using System.Linq; | |
using System.Text; | |
namespace System.Patterns.Repository | |
{ | |
public interface IUnitOfWorkStore | |
{ | |
IUnitOfWork ActiveUnitOfWork { get; set; } | |
} | |
public class ThreadedUnitOfWorkStore : IUnitOfWorkStore | |
{ | |
object _syncRoot = new object(); | |
IUnitOfWork _unitOfWork; | |
Dictionary<int, List<IUnitOfWork>> _units = new Dictionary<int, List<IUnitOfWork>>(); | |
public ThreadedUnitOfWorkStore() | |
{ | |
if (_syncRoot == null) | |
_syncRoot = new object(); | |
if (_units == null) | |
_units = new Dictionary<int, List<IUnitOfWork>>(); | |
} | |
public virtual IUnitOfWork ActiveUnitOfWork | |
{ | |
get | |
{ | |
lock (_syncRoot) | |
if (_units.Count > 0 && _units.ContainsKey(Thread.CurrentThread.ManagedThreadId) && _units[Thread.CurrentThread.ManagedThreadId].Count > 0) | |
return _units[Thread.CurrentThread.ManagedThreadId].Last(); | |
return null; | |
} | |
set | |
{ | |
lock (_syncRoot) | |
{ | |
if (value == null) | |
{ | |
_unitOfWork = value; | |
return; | |
} | |
value.UnitOfWorkCommitting += OnUnitOfWorkCommitting; | |
value.UnitOfWorkCanceling += OnUnitOfWorkCanceling; | |
if (_units.ContainsKey(value.ThreadId)) | |
{ | |
if (_units[value.ThreadId].Any(u => u.DataContextHandle == value.DataContextHandle)) | |
return; | |
_units[value.ThreadId].Add(value); | |
} | |
else | |
_units.Add(value.ThreadId, new List<IUnitOfWork>() { value }); | |
} | |
} | |
} | |
private void OnUnitOfWorkCanceling(IUnitOfWork uow) | |
{ | |
lock (_syncRoot) | |
{ | |
if (uow != null && _units.ContainsKey(uow.ThreadId)) | |
{ | |
if (_units[uow.ThreadId].Count > 1 && _units[uow.ThreadId].Any(u => u.DataContextHandle == uow.DataContextHandle)) | |
{ | |
_units[uow.ThreadId].RemoveAll(dc => dc.DataContextHandle == uow.DataContextHandle); | |
} | |
else | |
_units.Remove(uow.ThreadId); | |
} | |
} | |
} | |
void OnUnitOfWorkCommitting(IUnitOfWork uow) | |
{ | |
lock (_syncRoot) | |
{ | |
if (uow != null && _units.ContainsKey(uow.ThreadId)) | |
{ | |
if (_units[uow.ThreadId].Count > 1 && _units[uow.ThreadId].Any(u => u.DataContextHandle == uow.DataContextHandle)) | |
{ | |
_units[uow.ThreadId].RemoveAll(dc => dc.DataContextHandle == uow.DataContextHandle); | |
} | |
else | |
_units.Remove(uow.ThreadId); | |
} | |
} | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Data.Entity; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using System.Data; | |
namespace System.Data.Entity.Repository | |
{ | |
public delegate void UnitOfWorkCommitting(IUnitOfWork uow); | |
public delegate void UnitOfWorkCanceling(IUnitOfWork uow); | |
public enum LockType | |
{ | |
Safe = 0, | |
Unsafe, | |
Locked | |
} | |
public class UnitOfWork : IUnitOfWork | |
{ | |
object syncRoot = new object(); | |
public UnitOfWork(string name, IServiceLocator locator, LockType lockType = LockType.Safe) | |
{ | |
this.DataContextHandle = Guid.NewGuid(); | |
this.context = locator.Resolve<DbContext>(name); | |
if (lockType == LockType.Locked) | |
isolationLevel = IsolationLevel.Serializable; | |
else if (lockType == LockType.Unsafe) | |
isolationLevel = IsolationLevel.ReadUncommitted; | |
else | |
isolationLevel = IsolationLevel.ReadCommitted; | |
} | |
DbContext context; | |
private IsolationLevel isolationLevel = IsolationLevel.ReadUncommitted; | |
private DbContextTransaction transaction; | |
public Guid DataContextHandle { get; private set; } | |
public int ThreadId { get; protected set; } = System.Threading.Thread.CurrentThread.ManagedThreadId; | |
public bool Committed { get; protected set; } | |
public bool ValidState | |
{ | |
get | |
{ | |
return context != null; | |
} | |
} | |
public event UnitOfWorkCommitting UnitOfWorkCommitting; | |
public event UnitOfWorkCanceling UnitOfWorkCanceling; | |
public IDisposable BeginTransaction() | |
{ | |
if (transaction != null) | |
throw new InvalidOperationException("This Data Context already has a Transaction."); | |
transaction = context.Database.BeginTransaction(isolationLevel); | |
return transaction; | |
} | |
public void Add<EntityType>(EntityType entity) where EntityType : class | |
{ | |
context.Set<EntityType>().Add(entity); | |
context.Entry(entity).State = EntityState.Added; | |
} | |
public void AddRange<EntityType>(IEnumerable<EntityType> entities) where EntityType : class | |
{ | |
context.Set<EntityType>().AddRange(entities); | |
foreach(var e in entities) | |
context.Entry(e).State = EntityState.Added; | |
} | |
protected void InvokeUnitOfWorkCommitting() | |
{ | |
lock (syncRoot) | |
{ | |
UnitOfWorkCommitting?.Invoke(this); | |
} | |
} | |
protected void InvokeUnitOfWorkCanceling() | |
{ | |
lock (syncRoot) | |
{ | |
UnitOfWorkCanceling?.Invoke(this); | |
} | |
} | |
public void Commit() | |
{ | |
context.SaveChanges(); | |
if (transaction != null) | |
transaction.Commit(); | |
InvokeUnitOfWorkCommitting(); | |
Committed = true; | |
} | |
public void Remove<EntityType>(EntityType entity) where EntityType : class | |
{ | |
context.Set<EntityType>().Remove(entity); | |
} | |
public void RemoveRange<EntityType>(IList<EntityType> entities) where EntityType : class | |
{ | |
context.Set<EntityType>().RemoveRange(entities); | |
} | |
public IQueryable<EntityType> Get<EntityType>() where EntityType : class | |
{ | |
return context.Set<EntityType>(); | |
} | |
public void Integrate<EntityType>(EntityType entity) where EntityType : class | |
{ | |
context.Entry(entity).State = EntityState.Modified; | |
} | |
public EntityType Create<EntityType>() where EntityType : class | |
{ | |
var entity = context.Set<EntityType>().Create(); | |
context.Entry(entity).State = EntityState.Added; | |
return entity; | |
} | |
public void Dispose() | |
{ | |
if (transaction != null) | |
transaction.Dispose(); | |
if (context != null) | |
context.Dispose(); | |
if (!Committed) | |
InvokeUnitOfWorkCanceling(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment