Search code examples
c#oopdesign-patternsdependency-injection

How can I define an Interface which includes the methods of my base class as well as the methods in my current class?


I have a base class which includes common CRUD functionality:

public class GeneralRepository<T> : IGeneralRepository<T>
{
    private readonly IConfiguration _config;
    private readonly string _tableName;

    public GeneralRepository(IConfiguration config, string tableName)
    {
        _config = config;
        _tableName = tableName;
    }

    public async virtual Task<OperationResult<T>> InsertModelAndReturnAsync(string storedProcedure, T parameters)
    {
        using IDbConnection connection = new SqlConnection(_config.GetConnectionString("Default"));

        try
        {
            var result = await connection.QueryFirstOrDefaultAsync<T>(storedProcedure, parameters, commandType: CommandType.StoredProcedure);
            if (result != null)
            {
                return OperationResult<T>.Success(result);
            }
            else
            {
                return OperationResult<T>.Failure("No records created.");
            }
        }
        catch (Exception ex)
        {
            return OperationResult<T>.Failure($"An error has occured: {ex.Message}");
        }
    }

    public async virtual Task<OperationResult<T>> DeleteModelAndReturnAsync(string storedProcedure, int id)
    {
        using IDbConnection connection = new SqlConnection(_config.GetConnectionString("Default"));

        try
        {
            var result = await connection.QueryFirstOrDefaultAsync<T>(storedProcedure, new { Id = id }, commandType: CommandType.StoredProcedure);
            if (result != null)
            {
                return OperationResult<T>.Success(result);
            }
            else
            {
                return OperationResult<T>.Failure("No records deleted.");
            }
        }
        catch (Exception ex)
        {
            return OperationResult<T>.Failure($"An error has occured: {ex.Message}");
        }
    }

    public async virtual Task<OperationResult<T>> UpdateModelAndReturnAsync(string storedProcedure, T parameters)
    {
        using IDbConnection connection = new SqlConnection(_config.GetConnectionString("Default"));

        try
        {
            var result = await connection.QueryFirstAsync<T>(storedProcedure, parameters, commandType: CommandType.StoredProcedure);

            return OperationResult<T>.Success(result);
        }
        catch (Exception ex)
        {
            return OperationResult<T>.Failure($"An error has occured: {ex.Message}");
        }
    }

    public async virtual Task<ICollection<T>> GetAllAsync()
    {
        try
        {
            using IDbConnection connection = new SqlConnection(_config.GetConnectionString("Default"));

            var parameters = new { TableName = _tableName };

            var query = """
                SELECT *
                FROM @TableName
                """;

            var result = (await connection.QueryAsync<T>(query, parameters)).ToList();

            return result;
        }
        catch (Exception ex)
        {
            throw;
        }
    }

    public async virtual Task<T?> GetByIdAsync(int id)
    {
        try
        {
            using IDbConnection connection = new SqlConnection(_config.GetConnectionString("Default"));

            var parameters = new { TableName = _tableName, Id = id };

            var query = """
                SELECT *
                FROM @TableName
                WHERE Id = @Id
                """;

            T? result = await connection.QueryFirstOrDefaultAsync<T>(query, parameters);

            return result;
        }
        catch (Exception ex)
        {
            throw;
        }
    }
}

With the following interface:

public interface IGeneralRepository<T>
{
    Task<OperationResult<T>> DeleteModelAndReturnAsync(string storedProcedure, int id);
    Task<ICollection<T>> GetAllAsync();
    Task<T?> GetByIdAsync(int id);
    Task<OperationResult<T>> InsertModelAndReturnAsync(string storedProcedure, T parameters);
    Task<OperationResult<T>> UpdateModelAndReturnAsync(string storedProcedure, T parameters);
}

Then, I have a repository to execute more table-specific queries:

public class LoginRepository : GeneralRepository<LoginModel>, ILoginRepository
{
    private readonly IConfiguration _config;
    public const string TableName = "Login";
    public LoginRepository(IConfiguration config) : base(config, TableName)
    {
        _config = config;
    }

    public Task Test()
    {         
        throw new NotImplementedException();
    }
}

I have defined ILoginRepository as follows:

public interface ILoginRepository : IGeneralRepository<LoginModel>
{
    Task Test();
}

The problem I am encountering is that I get an error stating that LoginRepository doesn't fulfill the contract defined in IGeneralRepository<LoginModel>.

Severity    Code    Description Project File    Line    Suppression State
Error   CS0738  'LoginRepository' does not implement interface member 'IGeneralRepository<LoginModel>.DeleteModelAndReturnAsync(string, int)'. 'GeneralRepository<LoginModel>.DeleteModelAndReturnAsync(string, int)' cannot implement 'IGeneralRepository<LoginModel>.DeleteModelAndReturnAsync(string, int)' because it does not have the matching return type of 'Task<OperationResult<LoginModel>>'.

I assumed that since I am inheriting the methods of GeneralRepository, that contract would be fulfilled.

I need an interface that gives access to the methods on the current class as well as the parent class because I am trying to check this Repository into DI: builder.Services.AddScoped<ILoginRepository, LoginRepository>();.

If instead I define ILoginRepository as:

public interface ILoginRepository
{
    Task Test();
}

My code works, but now I cannot access base class methods in Dependency Injection.


Solution

  • Turns out the LoginModel class from Microsoft.AspNetCore.Identity.UI.V4.Pages.Account.Internal was auto-completed during my inheritance of GeneralRepository<LoginModel> in my LoginRepository class instead of my local LoginModel class. This lead to extremely confusing error messages.