Reputation: 5605
I am currently in the process of converting a large web service to use the repository pattern and dependency injection. We are expanding our team and the benefits of reliable unit testing outweighs the effort required to refactor the code.
I have chosen Ninject as my framework based on the recommendation of a colleague and have begun refactoring my code. This has involved creating a "Common" project that contains the objects themselves, a Repository.Database project to contain the data access logic, and a web service that uses both. I have used convention based mapping so that IPersonRepository should map to my concrete PersonRepository class.
I am currently taking the approach of creating a "Repository" property on each class with the [Inject] attribute, then replacing my constructors to use said repository, but have run into my first stumbling block and am not convinced I am doing things the right way. Before I started all this, I would instantiate an object like so:
var p = new Person(ID);
And using the format I suggested, my class looks something like this:
[Inject]
public IPersonRepository Repository { get; set; }
public string Name;
public Person(int ID)
{
// This feels wrong
var p = Repository.Get(ID);
Name = p.Name;
}
You can probably see my conundrum. How can I use a constructor without having to return a new object from the repository and then map each field to my current object? I can't replace "this", and while I could use something like AutoMapper to map each field in one go, it feels like I am doing something inherently wrong here.
I could use a static method instead of an injector:
[Inject]
public static IPersonRepository Repository { get; set; }
public string Name;
public static Person GetByID(int ID)
{
return Repository.Get(ID);
}
But as you can see it requires making Repository static, and it feels like I should be using a constructor rather than a static "GetByID" method. That could just be because I am so used to using a constructor.
Alternatively I can pass the Repository into the Person constructor, but again, it feels messy to do that every time I instantiate a Person in code.
What I am trying to achieve is to have my existing WCF project load all of its data using one repository, and for my Unit Test project to load all its data using another. I don't want to have to pass concrete implementations of IPersonRepository in either. Is this achievable and even recommended?
Upvotes: 0
Views: 1843
Reputation: 9820
I've created a small helper class : "EntityResolver" which implements the "IValueResolver" interface which was introduced recently in AutoMapper. This helper class can retrieve an entity from the repository when an Id is supplied.
1) AutoMapper configuration to map a ViewModel to an Entity is defined as follows:
Mapper.CreateMap<EmployeeVM, Employee>()
.ForMember(e => e.EmployeeNumber, opt => opt.MapFrom(vm => vm.Number))
// Other propeties omitted
.ForMember(e => e.Company, opt => opt.ResolveUsing<EntityResolver<Company>>().FromMember(vm => vm.CompanyId))
;
2) Code for the EntityResolver
public class EntityResolver<TEntity> : IValueResolver where TEntity : class, IEntity, new()
{
public ResolutionResult Resolve(ResolutionResult source)
{
return source.New(ResolveObject(source));
}
private object ResolveObject(ResolutionResult source)
{
if (!source.Context.Options.Items.ContainsKey("Services")) return null;
var services = (List<object>)source.Context.Options.Items["Services"];
var item = services.FirstOrDefault(s => s is IBaseService<TEntity>);
if (item == null) return null;
var id = (long)source.Value;
if (id <= 0) return null;
var service = (IBaseService<TEntity>)item;
return service.GetById(id);
}
}
3) When mapping the ViewModel to an Entity, I supply additional IMappingOperationOptions
Mapper.Map<TEntity>(viewModel, opt => opt.Items["Services"] = GetServices());
4) The GetServices method just returns all services needed to resolve the entities used in the Employee object.
protected override List<object> GetServices()
{
var services = base.GetServices();
services.Add(_companyService);
services.Add(_functionService);
services.Add(_subfunctionService);
services.Add(_countryService);
return services;
}
For more details see my test project.
Upvotes: 0
Reputation: 910
Your entities should not need to know anything about where or how they are stored. The idea of the repository pattern is to take the responsibility of persistence away from the business logic. Practically this means you would set up your service as follows:
This would then have the added benefit of making your web service testable by injecting a mocked repository and not having to worry about how the entities would get retrieved in your code itself.
Upvotes: 1