Общий репозиторий с несколькими DTO в один объект (Automapper)

Я работаю над проектом, в котором у меня есть общий уровень сервиса и репозитория. Я использую Automapper для сопоставления DTO с моделями сущностей. Одна сущность может иметь один или несколько DTO. Моя проблема в том, как мне сообщить моему универсальному классу репозитория, какой DTO должен вернуться на сервисный уровень?

Объект

[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; }
}

DTO

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; }
}

Универсальный репозиторий

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);
}

Общая реализация репозитория

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;                           
    }
}

Общая услуга

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);
}

Общая реализация службы

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();
    }
}

Репозиторий и служба объектов

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;
    }
}

Контроллер Я хочу вызвать метод findBy в общей службе и выбрать, какой DTO будет возвращен. Класс Contract был частью моего тестирования, но он не работает. Должен ли я использовать рефлексию, чтобы передать каждому методу в службе тип DTO, который я хочу?

[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);
    }
}

Большое спасибо заранее! И я надеюсь, что этот вопрос может помочь и другим людям!

Хороших выходных!

Лучано


person Luis Miranda    schedule 19.05.2017    source источник
comment
Теперь вы столкнулись с последствиями попытки сделать все в общем виде. В программировании все зависит от контекста, вы сначала разделяете все, а затем, когда вы видите какой-то общий шаблон, вы применяете общие подходы.   -  person Fabio    schedule 19.05.2017
comment
Спасибо @Fabio за ваш ответ. У меня уже есть несколько конкретных репозиториев и сервисов, которые делают определенные вещи. Я хочу использовать общие для выполнения некоторых простых операций CRUD. Если я создам findBy для каждого репозитория, у меня будет точно такой же код для каждой сущности, но с разным типом возврата (разные контракты). Я хочу избежать такой ситуации. Спасибо!   -  person Luis Miranda    schedule 19.05.2017
comment
Я не думаю, что будет способ сделать это без отражения, если вы хотите сделать в одном методе getEntityContract, тогда этот метод должен знать о возвращаемом типе либо в качестве параметра, либо в заголовке (что бы вы ни чувствовали) , а затем используйте отражение для выполнения findBy, используя универсальное выполнение. А также, пожалуйста, измените имена URL-адресов, например, в соответствии с REST, URL-адрес должен выглядеть как api/entities/byfilter, а не как api/entity/getEntityByFilter. И, пожалуйста, прокомментируйте, если вы найдете другой способ.   -  person Prince    schedule 19.05.2017
comment
Спасибо @Prince за совет, я уже изменил все свои URL-имена, как вы мне сказали. Я нашел обходной путь, я разместил как ответ!. Спасибо!   -  person Luis Miranda    schedule 27.07.2017


Ответы (1)


Извините за задержку с ответом, но в любом случае, вот.

Я нашел способ сделать это, указав методу, какой объект я хочу вернуть. Итак, мой репозиторий выглядит так

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;                           
    }
}

Итак, теперь каждый раз, когда мне нужно вызывать репозиторий, я вызываю его так в классе обслуживания:

//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);

Как видите, общий тип DTO больше не на уровне класса, а на уровне метода.

Надеюсь, это поможет другим людям, и если у вас есть лучшее решение, сообщите мне.

Ваше здоровье!

person Luis Miranda    schedule 27.07.2017