前几天看公司一个新项目的底层使用了dapper,你们都知道dapper是一个很是强大的半自动化orm,帮程序员解决了繁琐的mapping问题,用起来很是爽,但我仍是遇到了一件很是不爽的事情,以下代码所示:程序员
public class UserDAL : BaseDAL { public List<UserModel> GetList() { using (SqlConnection conn = new SqlConnection(ConnectionString)) { var list = conn.Query<UserModel>("select * from users").ToList(); return list; } } public bool Insert() { using (SqlConnection conn = new SqlConnection(ConnectionString)) { var execnum = conn.Execute("insert into xxx "); return execnum > 0; } } public bool Update() { using (SqlConnection conn = new SqlConnection(ConnectionString)) { var execnum = conn.Execute("update xxx ...."); return execnum > 0; } } } public class UserModel {}
扫一下代码是否是总感受哪里不对劲,是的,为了能使用上Dapper的扩展方法,这里面每一个方法中都配上了模板化的 using (SqlConnection conn = new SqlConnection(ConnectionString))
,虽然这样写逻辑上没有任何问题,但我有洁癖哈,接下来试着封装一下,嘿嘿,用更少的代码作更多的事情。sql
仔细看上面的模板代码你会发现,真正的业务逻辑是写在 using
中的,而该块中只须要拿到一个 conn
就能够了,其余的统一提取封装到父类中,这就能够用到 委托函数
啦,对不对,用这个思路代码修改以下:api
public class BaseDAL { protected string ConnectionString { get; set; } public T Execute<T>(Func<SqlConnection, T> func) { using (SqlConnection connection = new SqlConnection(ConnectionString)) { return func(connection); } } }
有了父类通用的 Execute
方法,接下来子类中就能够直接用它啦,改造以下:app
public class UserDAL : BaseDAL { public List<UserModel> GetList() { return Execute((conn) => { var list = conn.Query<UserModel>("select * from users").ToList(); return list; }); } public bool Insert() { return Execute((conn) => { var execnum = conn.Execute("insert into xxx "); return execnum > 0; }); } public bool Update() { return Execute((conn) => { var execnum = conn.Execute("update xxx ...."); return execnum > 0; }); } }
改造以后代码是否是清晰多了,仅仅这一个通用方法貌似还不行,起码 ConnectionString
不能框死。异步
相信有很多朋友的公司是作 ToB 的业务,通常是一个商家一个DB的设计思路,这里就须要在 Execute 上增长一个 ConnectionString 字符串参数,你能够经过重载方法 或者 可选参数,改造以下:函数
public T Execute<T>(Func<SqlConnection, T> func) { return Execute(ConnectionString, func); } public T Execute<T>(string connectionString, Func<SqlConnection, T> func) { using (SqlConnection connection = new SqlConnection(connectionString ?? ConnectionString)) { return func(connection); } } public class UserDAL : BaseDAL { public List<UserModel> GetList(string connectionString) { return Execute(connectionString, (conn) => { var list = conn.Query<UserModel>("select * from users").ToList(); return list; }); } }
这样看起来就舒服多了,不过还有一个问题,咱们的程序是给客户独立部署的,越简单越好,不然实施人员会砍人的,因此不少用户操做和api轨迹行为都记录到了sqlserver中,这里就有一个 业务表
和 一个 事务日志表
,并且要做为原子化提交,这里就涉及到了事务操做。sqlserver
由于有同时插入两张表的业务逻辑,免不了使用 transaction,接下来继续扩展 Execute 方法,代码以下:设计
public T Execute<T>(Func<SqlConnection, SqlTransaction, T> func) { return Execute(ConnectionString, func); } public T Execute<T>(string connectionString, Func<SqlConnection, SqlTransaction, T> func) { using (SqlConnection connection = new SqlConnection(connectionString ?? ConnectionString)) { connection.Open(); using (var transaction = connection.BeginTransaction()) { return func(connection, transaction); } } }
上面的代码应该很好理解,将 transaction
做为回调函数的参数,业务逻辑部分直接将 transaction
塞入到各自的业务代码中便可,子类能够改造以下:日志
public bool Insert() { return Execute((conn, trans) => { var execnum = conn.Execute("insert into xxx ", transaction: trans); if (execnum == 0) return false; var execnum2 = conn.Execute("update xxx set xxx", transaction: trans); if (execnum2 > 0) trans.Commit(); return execnum > 0; }); }
这样 Execute
对 transaction 的支持貌似也差很少了,异步版的我就不在此封装啦。code
文章来源于工做中的点点滴滴,这也是个人即兴封装,你们要是有更好的封装代码,欢迎交流,独乐乐不如众乐乐,本篇就说到这里啦,但愿对您有帮助。