I am trying to create a new core framework (web mostly) with Repository and Unit Of Work pattern for my applications that i can able to change my ORM to NHibernate or Dapper later on.
Right now my interface of Unit of work is like this :
public interface IUnitOfWork : IDisposable
{
void Commit();
void Rollback();
}
And Entity Framework implementation is like this (trimmed for readability)
public class EfUnitOfWork : IUnitOfWork
{
....
public EfUnitOfWork(ApplicationDbContext context)
{
this._context = context;
this._transaction = new EfTransaction(_context.Database.BeginTransaction());
}
public void Commit()
{
this._context.SaveChanges(true);
this._transaction.Commit();
...
}
public void Rollback()
{ ...
}
}
The problem is that in my Service Layer that contains business logic i can do something like this with the navigations properties:
public bool CreateCity(CityCreateModel model)
{
using (var uow = _unitOfWorkFactory.Create())
{
var city = new City();
city.Name = model.Name;
city.State = new State() { Country = new Country() { Name = "SomeCountry" }, Name = "SomeCity" };
_cityRepository.Create(city);
try
{
uow.Commit();
return true;
}
catch (Exception)
{
uow.Rollback();
throw;
}
}
}
The repository Create method is pretty straightforward as i use entity framework :
public void Create(City entity)
{
_set.Add(entity);
}
The problem begins here , when a member of team writes a code like the Service example with using new keyword on navigation properties or adding items for collection navigation properties, entity framework detects these changes and when i save changes, these are also saved to the database.
If i try to change existing sample to Dapper.NET or to a REST service later on there can be a LOT of problems that i had to go look for every navigation property and track that they have been changed or not and write a lot of (possibly garbage) code for them as i didn't really know what is inserted on the table via entity framework and what isnt (because of navigation properties are also inserted and my repositories called once for only 1 insert that is for City in my example above)
Is there a way to prevent this behavior or is there a pattern known that i can adapt early on so i won't have problems later on?
How did you overcome this?
Before I begin I want to give some notes to your code:
public EfUnitOfWork(ApplicationDbContext context)
{
this._context = context;
this._transaction = new EfTransaction(_context.Database.BeginTransaction());
}
1) From your example I can see that you are sharing the same DbContext(given as parameter in the constuctor for the whole application. I do not think this is a good idea, because the entities will be cached in the first level cache and the change tracker will track them all. With this approach will get soon performance problems when the database will be growth.
_cityRepository.Create(city);
public void Create(City entity)
{
_set.Add(entity);
}
2) The base repository should be generic of type T where T is an entity! and so you can create a city;
var city = _cityRepository.Create();
Fill the city or provide the data as parameters in the create method.
Back to your question:
Is there a way to prevent this behavior or is there a pattern known that i can adapt early on so i won't have problems later on?
Each ORM has his own desgin concept and it is not easy to find generic way which fit to them all that way I would do the following:
1) Separate the repository contracts in one the assembly (contracts dll)
2) For each ORM Framework use a separate assembly which implement the repository contracts.
Example:
public interface ICityRepository<City> :IGenericRepsotiory<City>
{
City Create();
Find();
....
}
Entity Frmework assembly:
public class CityRepositoryEF : ICityReposiory { ..
Dapper Frmework assembly:
public class CityRepositoryDapper : ICityReposiory { ..