简体   繁体   中英

Using interface and abstract class in repository pattern in ASP.NET Core

We are currently using dapper ORM to access data by calling store procedures. The current code is having a class BusinessFunctions which inherits another class DataFunctions which are having helper methods to execute the stored procedures.

I am not happy with this code. It's just too rigid and not future proof. And above all it's not coded to an interface rather coded to an implementation. I propose an interface IRepository with an abstract class Repository which implements all helper generic methods. Then I create BusinessRepository that implements the abstract Repository class and call the generic helpers method. Again, my colleague is telling me to remove the IRepository interface and just use the Repository abstract class.

public class BusinessFunctions : DataFunctions
{
    public BusinessFunctions(ConnectionManager conMgr, LogWriter logWriter, AppUser appUser) : base(conMgr, logWriter, appUser)
    {

    }

    public async Task<Business> FindAsync(int businessId)
    {
        throw new NotImplementedException();
    }

    public async Task<Business> FindAsync(string businessGuid)
    {
        var lst = await StoredProcQueryAsync<Business>("spBusinessGetSetupGUID", new { BusinessGuid = businessGuid });

        if (lst.Count() == 0)
            throw new NotFoundInDatabaseException("Business", businessGuid);
        else
            return lst.Single();
    }

    public async Task<bool> IsHostedTokenizeCardAllowedAsync(string businessGuid)
    {
        var b = await FindAsync(businessGuid);
        if (b.HostedPaymentEnabled)
            return true;
        else
            return false;
    }
}



 public class DataFunctions : IDisposable
{
    private ConnectionManager _conMgr;
    private LogWriter _logWriter;
    private AppUser _appUser;

    public ConnectionManager ConnMgr
    {
        get { return _conMgr; }
    }

    protected LogWriter Logger
    {
        get { return _logWriter; }
    }

    protected AppUser User
    {
        get { return _appUser; }
    }

    public DataFunctions(ConnectionManager conMgr, LogWriter logWriter, AppUser appUser)
    {
        _conMgr = conMgr;
        _logWriter = logWriter;
        _appUser = appUser;
    }

    public void Dispose()
    {

    }

    public async Task StoredProcExecuteNonQueryAsync(string storedProc,
        List<StoredProcParameter> storedProcParameters = null,
        SqlCommandTimeout commandTimeout = SqlCommandTimeout.Default,
        SqlAccessType accessType = SqlAccessType.ReadWrite
        )
    {
        using (SqlConnection conn = new SqlConnection(ConnMgr.SqlConnectionString))
        {
            await conn.OpenAsync();
            await StoredProcExecuteNonQueryAsync(conn,
                storedProc, 
                storedProcParameters: storedProcParameters, 
                commandTimeout: commandTimeout,
                accessType: accessType);
        }
    }

    public async Task StoredProcExecuteNonQueryAsync(SqlConnection conn, 
        string storedProc,
        List<StoredProcParameter> storedProcParameters = null,
        SqlCommandTimeout commandTimeout = SqlCommandTimeout.Default,
        SqlAccessType accessType = SqlAccessType.ReadWrite,
        SqlTransaction trans = null
        )
    {
        using (SqlCommand cmd = new SqlCommand(storedProc, conn))
        {
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.CommandTimeout = (int)commandTimeout;
            if (trans != null) cmd.Transaction = trans;
            if (storedProcParameters != null)
            {
                foreach(var p in storedProcParameters)
                {
                    cmd.Parameters.Add(p.ToSqlParameter());
                }
            }
            await cmd.ExecuteNonQueryAsync();
        }
    }

    public async Task<IEnumerable<T>> StoredProcQueryAsync<T>(string storedProc,
        object storedProcParameters = null,
        SqlCommandTimeout commandTimeout = SqlCommandTimeout.Default,
        SqlAccessType accessType = SqlAccessType.ReadWrite)
    {
        using (SqlConnection conn = new SqlConnection(ConnMgr.SqlConnectionString))
        {
            conn.Open();
            return await StoredProcQueryAsync<T>(conn,
                storedProc,
                storedProcParameters,
                commandTimeout);
        }
    }

    public async Task<IEnumerable<T>> StoredProcQueryAsync<T>(SqlConnection conn,
        string storedProc,
        object storedProcParameters = null,
        SqlCommandTimeout commandTimeout = SqlCommandTimeout.Default)
    {
        return await conn.QueryAsync<T>(storedProc,
                commandType: CommandType.StoredProcedure,
                commandTimeout: (int)commandTimeout,
                param: storedProcParameters);


    }
}

I think the reason you're unhappy with the code is that it seems to be intermingling service functionality into the repository layer. The repository layer should simply call the stored procedure.

public async Task<bool> IsHostedTokenizeCardAllowedAsync(string businessGuid)
{
    var b = await FindAsync(businessGuid);
    if (b.HostedPaymentEnabled)
        return true;
    else
        return false;
}

This for example is a good candidate to be in the service layer.

Your repo layer should really just have your ConnectionManager or a Connection factory injected via IoC.

The trick we use is to put an attribute on data model fields that we know are going to be stored procedure parameters (usually most or all). Then we have an extension method that reflects over the attributes and pulls the fields, values, and types creating a dapper DynamicParameters object. Most of our repository calls look like this:

 public async Task<User> AddUserAsync(UserAdd user) 
 {
    using (var connection = _connectionFactory.Create() 
      {
         var result = connection.ExecuteAsync("dbo.AddUser", user.GetParameters(), commandType: CommandType.StoredProcedure";
         return result;
      }
  }

It's relatively quick and easy to use. Gets are very easy to test. Inserts/Deletes/Updates not so much. You get into needing to mock the SqlConnection which can be problematic.

In addition, if you get into more complex areas that are subject to change, you can use the strategy pattern to move methods into their own classes. Below would be an example of splitting your add method into its own class:

 public class MyRepository
 {
    private readonly IAddMethod<UserAdd> _addMethod;
    private readonly IConnectionFactory _connectionFactory;

    public MyRepository(IAddMethod<UserAdd> userAddMethod, 
      IConnectionFactory connectionFactory) 
    {
       //..guard clauses, assignments, etc.
    }

    public async Task<int> AddAsync(UserAdd user)
    {
        return _addMethod.AddAsync(user);
    }
 }

You can even decorate these strategy methods in IoC to hide/augment them as needed. (in structuremap it's .DecorateAllWith()

In short, move any logic to the service layer, consider a generic extension method for creating your DynamicParameters list, and inject the connection factory via IoC. I think you'll find the separation of concerns will simplify things significantly.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM