首页 > 解决方案 > 通用基本控制器中的通用服务

问题描述

为了避免 DRY,我冒险尝试为我的所有控制器生成一个通用基类。在插入服务类之前一切都很好。我的基本控制器是:

基本控制器.cs

public class BaseController<TEntity, Tdto, TKey> : Controller
{
    protected TavoraContext _context;
    protected IMapper _mapper;

    private IGeneric<TEntity, TKey, Tdto> _srv;

    public BaseController(IGeneric<TEntity, TKey, Tdto> srv)
    {
        _srv = srv;
    }

然后,在其中一个控制器中:

公司控制器.cs

public class CompaniesController : BaseController<Company, CompanySimpleDTO, long>
{

    public CompaniesController(TavoraContext context, IMapper mapper, CompaniesService companiesService) : base(companiesService)
    {
    }

CompaniesService 从实现 IGeneric 的 GenericService 继承,所以在我看来应该没有错误,我得到“不可能从 CompaniesService 转换为 IGeneric”

公司服务.cs

public class CompaniesService : GenericService<Company, long, CompanyDTO>
{

    public CompaniesService(TavoraContext context, IMapper mapper)  : base(context, mapper)
    {

        _runner = new RunnerWriteDb<CompanyDTO, Company>(
            new WriteCompanyAction(
                new WriteCompanyDBAccess(context), mapper), context);

    }

通用服务.cs

public class GenericService<TEntity, TKey, Tdto> : IGeneric<TEntity, TKey, Tdto> where TEntity : BaseEntity<TKey>
{
    protected RunnerWriteDb<Tdto, TEntity> _runner; 

    protected readonly int PAGESIZE = 20;
    protected readonly TavoraContext _context;
    protected DbSet<TEntity> _currentEntity;
    protected IMapper _mapper;

    public GenericService(TavoraContext context, IMapper mapper)
    {
        _context = context;
        _currentEntity = _context.Set<TEntity>();
        _mapper = mapper;
    }

IGeneric.cs

public interface IGeneric<TEntity, TKey, Tdto>
{
    IQueryable<TEntity> GetAll();
    IQueryable<DTO> GetAll<DTO>();

    //void Add(TEntity newItem);
    //void AddRange(List<TEntity> newItems);

    bool Update(TEntity updateItem);
    void UpdateRange(List<TEntity> updateItems);

    bool Delete(TKey id);
    bool DeleteRange(List<TEntity> removeItems);

    TEntity GetById(TKey id);

    RunnerWriteDbResult<TKey> Write(Tdto dto);
}

标签: c#generics.net-corerepository-pattern

解决方案


问题在于你IGeneric<TEntity, TKey, Tdto>是不变的。Tdto这意味着它在泛型类型中既不是协变的也不是逆变的。

从文档中:

协变和逆变是指使用比最初指定的更多派生类型(更具体)或更少派生类型(更具体)的能力的术语。泛型类型参数支持协变和逆变,以便在分配和使用泛型类型时提供更大的灵活性。当您提到类型系统时,协变、逆变和不变性具有以下定义。这些示例假定一个名为 Base 的基类和一个名为 Derived 的派生类。

协方差

使您能够使用比最初指定更多的派生类型。

您可以将 IEnumerable 的实例(在 Visual Basic 中为 IEnumerable(Of Derived))分配给 IEnumerable 类型的变量。

逆变

使您能够使用比最初指定的更通用(较少派生)的类型。

您可以将 Action 实例(Visual Basic 中的 Action(Of Base))分配给 Action 类型的变量。

不变性

意味着您只能使用最初指定的类型;所以一个不变的泛型类型参数既不是协变的也不是逆变的。

您不能将 List 的实例(Visual Basic 中的 List(Of Base))分配给 List 类型的变量,反之亦然。

(来源:https ://docs.microsoft.com/en-us/dotnet/standard/generics/covariance-and-contravariance )

由于您的IGeneric接口在所有泛型类型参数中都是不变的,因此不允许您尝试 ( IGeneric<Company, long, CompanySimpleDTO>to IGeneric<Company, long, CompanyDTO>) 的强制转换,因为它不能保证它的Tdto类型参数只进入接口。

因此,如果您将其更改IGeneric<TEntity, TKey, Tdto>为:

public interface IGeneric<TEntity, TKey, in Tdto> where TEntity: BaseEntity<TKey>
{
    IQueryable<TEntity> GetAll();
    IQueryable<DTO> GetAll<DTO>();

    //void Add(TEntity newItem);
    //void AddRange(List<TEntity> newItems);

    bool Update(TEntity updateItem);
    void UpdateRange(List<TEntity> updateItems);

    bool Delete(TKey id);
    bool DeleteRange(List<TEntity> removeItems);

    TEntity GetById(TKey id);
}

它应该工作。不过这里要注意两点:

  1. 我假设CompanyDTO继承自CompanySimpleDTO.

  2. 接口的泛型类型Tdto不在IGeneric接口的任何地方使用。如果这是错误的,并且它的使用方式实际上也需要脱离界面,那么这个解决方案将不起作用,恐怕你需要重新设计一下。


推荐阅读