Using interface and abstract class in repository pattern in ASP.NET Core
我们目前正在使用Dapper ORM通过调用存储过程来访问数据。当前代码具有一个类BusinessFunctions,它继承另一个类数据函数,这些类数据函数具有执行存储过程的助手方法。
我对这个代码不满意。它太死板了,不足以证明未来。最重要的是,它不是被编码到接口上,而是被编码到一个实现上。我提出了一个与抽象类存储库的接口IRepository,它实现了所有助手通用方法。然后,我创建了实现抽象存储库类的BusinessRepository,并调用通用的helpers方法。同样,我的同事告诉我删除iRepository接口,只使用知识库抽象类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | 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); } } |
我认为您对代码不满意的原因是它似乎将服务功能混合到了存储库层中。存储库层应该简单地调用存储过程。
1 2 3 4 5 6 7 8 | public async Task<bool> IsHostedTokenizeCardAllowedAsync(string businessGuid) { var b = await FindAsync(businessGuid); if (b.HostedPaymentEnabled) return true; else return false; } |
例如,这是一个很好的服务层候选者。
您的repo层应该只通过ioc注入ConnectionManager或连接工厂。
我们使用的技巧是在数据模型字段上放置一个属性,我们知道这些字段将是存储过程参数(通常是大部分或全部)。然后,我们有一个扩展方法,它对属性进行反射,并提取字段、值和类型,从而创建一个dapper dynamicParameters对象。我们的大多数存储库调用如下所示:
1 2 3 4 5 6 7 8 | 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; } } |
号
这是相对快速和易于使用。GET很容易测试。插入/删除/更新不多。您需要模拟可能有问题的sqlconnection。
此外,如果您进入更复杂的领域,这些领域可能会发生变化,那么您可以使用策略模式将方法移动到它们自己的类中。下面是将添加方法拆分为自己的类的示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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); } } |
您甚至可以在IOC中修饰这些策略方法,以根据需要隐藏/增强它们。(在结构图中是.decorateAllWith())
简而言之,将任何逻辑移动到服务层,考虑一个用于创建动态参数列表的通用扩展方法,并通过IOC注入连接工厂。我想你会发现分离关注点会大大简化事情。