通用基础控制器中的通用服务

Generic service in Generic Base Controller

我为了避免 DRY 我冒昧地想到为我所有的控制器生成一个通用基础 class。一切都很好,直到插入服务 class。我的基地控制器是:

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

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

companiescontroller.cs

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

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

CompaniesService 继承自实现 IGeneric 的 GenericService,因此,在我看来应该没有错误,我得到“无法从 CompaniesService 转换为 IGeneric”

companiesservice.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);

    }

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

使用依赖注入将服务注入控制器。使用像 autofac 这样的 pkgs 来解决依赖关系

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

来自文档:

Covariance and contravariance are terms that refer to the ability to use a more derived type (more specific) or a less derived type (less specific) than originally specified. Generic type parameters support covariance and contravariance to provide greater flexibility in assigning and using generic types. When you are referring to a type system, covariance, contravariance, and invariance have the following definitions. The examples assume a base class named Base and a derived class named Derived.

Covariance

Enables you to use a more derived type than originally specified.

You can assign an instance of IEnumerable (IEnumerable(Of Derived) in Visual Basic) to a variable of type IEnumerable.

Contravariance

Enables you to use a more generic (less derived) type than originally specified.

You can assign an instance of Action (Action(Of Base) in Visual Basic) to a variable of type Action.

Invariance

Means that you can use only the type originally specified; so an invariant generic type parameter is neither covariant nor contravariant.

You cannot assign an instance of List (List(Of Base) in Visual Basic) to a variable of type List or vice versa.

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

由于您的 IGeneric 接口在其所有泛型类型参数中都是不变的,因此您尝试的转换(IGeneric<Company, long, CompanySimpleDTO>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. IGeneric接口的泛型Tdto在接口的任何地方都没有使用。如果那是错误的,并且它的使用方式实际上也需要离开界面,那么这个解决方案将行不通,恐怕您需要重新设计一下。