I was involved in some code-review discussions about UnitOfWork
and Repository
pattern implementation with EF, which put me in some sort of confusion.
Yes, I know that EF already implements UnitOfWork
and Repository
pattern, but the question is not about that. I am just asking for your opinion regarding existing code base.
Let's start with IUnitOfWork
interface declaration:
public interface IUnitOfWork : IDisposable
{
void CommitTransaction();
IEnumerable<DbEntityValidationResult> Errors();
dynamic Repository<TEntityType>() where TEntityType : BaseEntity;
}
I do not really like that
IUnitOfWork
declared asIDisposable
. For me it looks like leaky abstraction, why should my UnitOfWork beDisposable
if it isn't by his nature?I also do not like the
Errors()
method. First of all it introduces dependency onDbEntityValidationResult
class, which I do not want in my genericIUnitOfWork
interface. I would prefer my newly createdCommit()
method to return my customValidationResult
class, which will act as mapping forDbEntityValidationResult
.Something like this:
IEnumerable<ValidationResult> Commit();
The last thing is return value of
Repository<T>()
method. I don't like dynamic object, which is returned. I want some interface to be returned from such method, likeIRepository<T>
(I will ask about that a bit later).
Now let's move to the implementation. I will highlight some thing, that looks strange to me.
As soon as IUnitOfWork
is declared as IDisposable
the concrete implementation of IUnitOfWork
implements some sort of Disposable
pattern:
private bool _disposed;
~UnitOfWork()
{
SharedDispose(false);
}
public void Dispose()
{
SharedDispose(true);
GC.SuppressFinalize(this);
}
private void SharedDispose(bool disposing)
{
if (_disposed || !disposing)
{
return;
}
_context.Dispose();
_disposed = true;
}
I do not see any reasons to have all this stuff. As soon as we do not own any unmanaged resources, we can skip all these destructors and all these instructions for GC
to skip finalization queue for our unit of work object. Yes, we still need to implement IDisposable
as soon as EF DbContext
(which we own internally) implements it, but the implementation can be so much simpler:
public void Dispose()
{
_context.Dispose();
_context = null;
}
Now let's see dynamic Repository<T>()
implementation:
public dynamic Repository<TEntityType>() where TEntityType : BaseEntity
{
if (_repositories == null)
{
_repositories = new Dictionary<string, object>();
}
var type = typeof(TEntityType).Name;
var repositoryTypeName = typeof(UnitOfWork).Namespace + ".Repositories." + type + "Repository";
var repoType = Type.GetType(repositoryTypeName);
if (!_repositories.ContainsKey(type))
{
object repositoryInstance;
if (repoType != null)
{
repositoryInstance = Activator.CreateInstance(repoType, _context);
}
else
{
var repositoryType = typeof(GenericRepository<>);
repositoryInstance = Activator.CreateInstance(repositoryType.MakeGenericType(typeof(TEntityType)), _context);
}
_repositories.Add(type, repositoryInstance);
}
return repoType == null ? (GenericRepository<TEntityType>)_repositories[type] : Convert.ChangeType(_repositories[type], repoType);
}
The method uses reflection to create specific repository for T-entity or returns
GenericRepository<T>
if there is no specific implementation defined. So the question is that a good practice to use reflection for that?Another thing, why do we need this cache dictionary if unit of work object is injected in the scope of HTTP request?
How we can unify these repositories to get rid of dynamic object, which is returned right now?
My first idea was not to use specific repositories at all, instead create more sophisticated API for generic repository (which supports filtering, query object, paging and etc) and just return the interface like IRepository<T>
. But not really sure if it is a good solution?
Beside this, the solution is using two data sources - the one is EF/Database and another one is web services. Web service repositories cannot so easily mapped to the IRepository<T>
and most probably required some specific interface to be implemented.
What is the best practice to create repositories for two different data sources? How can we get rid of dynamic object? Do we need to create repositories for database and web services in the scope of one unit of work? Or should it be a separate one?
IDisposable
should be implemented by a class not by an interface. \$\endgroup\$