0
votes

I am working in a project where I have a generic service and repository layer. I'm using Automapper to map the DTOs to the entity models. One entity can have one or more DTOs. My problem is, how do I tell to my generic repository class which DTO should return to the service layer?

Entity

[Table("Entity")]
public class Entity
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public int id { get; set; }

    [Required]
    public string name { get; set; }

    [Required]
    public string surname { get; set; }
}

DTOs

public class Contract
{
}

[DataContract]
public class EntityContract: Contract
{
    [Required]
    [DataMember]
    public string name { get; set; }

    [Required]
    [DataMember]
    public string surname { get; set; }
}

[DataContract]
public class EntityPassportContract: Contract
{
    [Required]
    [DataMember]
    public string name { get; set; }

    [Required]
    [DataMember]
    public string surname { get; set; }

    [Required]
    [DataMember]
    public string passport { get; set; }
}

Generic repository

public interface IGenericRepository<E, DTO> 
    where E : class
    where DTO: class
{
    List<DTO> findBy(Expression<Func<DTO, bool>> query = null, Func<IQueryable<DTO>, IOrderedQueryable<DTO>> orderBy = null,
                                Expression<Func<DTO, bool>> whereIn = null, int? page = null, int? sizePage = null);
}

Generic repository implementation

public abstract class GenericRepository<C, E, DTO> : IGenericRepository<T, DTO> 
    where E : class
    where DTO: class
    where C : IdentityDbContext<User>, new()
{
    //...
    public virtual List<DTO> findBy(Expression<Func<DTO, bool>> query = null, Func<IQueryable<DTO>, IOrderedQueryable<DTO>> orderBy = null,
                                        Expression<Func<DTO, bool>> whereIn = null, int? page = null, int? sizePage = null)
    {
        //...transform DTO queries to E queries and Get IQueriable<E> entity from database
        DTO dtoEntity=entity.ProjectTo<DTO>();
        return dtoEntity;                           
    }
}

Generic service

public interface IService<E, DTO> : IService
    where T: class
    where DTO: class
{
    List<DTO> findBy(Expression<Func<DTO, bool>> query = null, Func<IQueryable<DTO>, IOrderedQueryable<DTO>> orderBy = null,
                                           Expression<Func<DTO, bool>> whereIn = null, int? page = null, int? sizePage = null);
}

Generic service implementation

public abstract class Service<E, DTO> : IService<T, DTO>
    where E: class
    where DTO: class
{

    protected IGenericRepository<E, DTO> repository;
    public Service(IGenericRepository<E, DTO> repository,)
    {
        this.repository = repository;
    }
    public virtual List<DTO> findBy(Expression<Func<DTO, bool>> query = null, Func<IQueryable<DTO>, IOrderedQueryable<DTO>> orderBy = null,
                                               Expression<Func<DTO, bool>> whereIn = null, int? page = null, int? sizePage = null)
    {
        return repository.findBy(query, orderBy, whereIn, page, sizePage).ToList();
    }
}

Entity repository and service

public interface IEntityRepository : IGenericRepository<Entity, Contract>
{
}
public class EntityRepository : GenericRepository<EntitiesDB, Entity, Contract>, IEntityRepository
{
    public EntityRepository(IMapper mapper):base(mapper){ }
}

//Service
public interface IEntityService : IService<Entity, Contract>
{
}
public class EntityService : Service<Entity, Contract>, IEntityService
{
    private IEntityRepository repo;

    public EntityService(IEntityRepository repo) : base(repo)
    {
        this.repo = repo;
    }
}

Controller I want to call the method findBy in the generic service and choose which DTO is going to return. The Contract class was part of my testing but it is not working. Do I have to use reflexion may be to pass each method in the service the type of DTO I want?

[RoutePrefix("api/entity")]
public class EntityController : ApiController
{
    public EntityController(IEntityService entityService)
    : base(entityService)
    {
    }
    [Route("getEntityByFilter", Name = "getEntityByFilter")]
    [HttpGet]
    public async Task<IHttpActionResult> getEntityContract(string filter)
    {
        EntityContract entityContract=entityService.findBy(**Parameters**); //Need to call lambda expressions here so i think i need somehow the type.
        return Ok(entityContract);
    }
    [Route("getEntityByFilter2", Name = "getEntityByFilter2")]
    [HttpGet]
    public async Task<IHttpActionResult> getEntityPassportContract(string filter)
    {
        EntityPassportContract entityPassportContract=entityService.findBy(**Parameters**); //Need to call lambda expressions here so i think i need somehow the type.
        return Ok(entityPassportContract);
    }
}

Thanks so much in advance! And I hope this question could help other people too!

Have a great weekend!

Luciano

1
Now you faced a consequences of trying to make "everything" in generic way. Programming is all about context, you first separate everything and then when you see some generic pattern you will apply generic approaches.Fabio
Thanks @Fabio for your answer. I already have some specific repositories and services doing some specific stuff. I want to use the generic ones to do some simple CRUDs operations. If I build a findBy for each repository, I will have exactly the same code for each entity but with different return type (different contracts). I want to avoid that situation. Thanks!Luis Miranda
I don't think there will be a way of doing this without reflection, if you want to do in one method 'getEntityContract' then this method should know about the type to return, either as parameter or in the header(whatever you feel), and then use reflection to execute the findBy using the generic execution. And also please change the URL names, like as per REST, URL should look like "api/entities/byfilter" not "api/entity/getEntityByFilter" And please comment if you find another way.Prince
Thanks @Prince for the advice, I already change all my URL names as you told me. I've found a work around, I posted as an answer!. Thanks!Luis Miranda

1 Answers

0
votes

Sorry for the delay in my answer but anyway, here it is.

The way I've found to do this, was by telling the method which entity I want to be returned. So, my repository look like this

public abstract class GenericRepository<C, E> : IGenericRepository<T> 
where E : class
where C : IdentityDbContext<User>, new()
{
    //...
    public virtual List<DTO> findBy<DTO>(Expression<Func<DTO, bool>> query = 
     null, Func<IQueryable<DTO>, IOrderedQueryable<DTO>> orderBy = null,
                                    Expression<Func<DTO, bool>> whereIn = 
      null, int? page = null, int? sizePage = null)
    {
        //...transform DTO queries to E queries and Get IQueriable<E> entity 
           from database
        DTO dtoEntity=entity.ProjectTo<DTO>();
        return dtoEntity;                           
    }
}

So now, everytime i have to call the repository, I call it like this in the service class:

//If I want the EntityListResumeDTO
List<EntityListResumeDTO> EntityListContract = repo.findBy<EntityListResumeDTO>(x=>x.id==1);

//If I want the EntityListCompleteDTO
List<EntityListCompleteDTO> EntityListContract = repo.findBy<EntityListCompleteDTO>(x=>x.id==1);

As you can see, the DTO generic type is not at class level anymore but at method level.

Hope this helps other people, and if you have a better solution please let me know.

Cheers!