一:背景
1. 讲故事
前几天看公司一个新我的项目的底层应用了 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))
,尽管这样写逻辑上没有任何问题,但我有洁癖哈,接下来试着封装一下,嘿嘿,用更少的代码做更多的事件。
二:模板化代码封装摸索
1. 将模板化的代码提取到父类中
认真看下面的模板代码你会发现,真正的业务逻辑是写在 using
中的,而该块中只须要拿到一个 conn
就能够了,其余的对立提取封装到父类中,这就能够用到 委托函数
啦,对不对,用这个思路代码批改如下:
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
办法,接下来子类中就能够间接用它啦,革新如下:
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
不能框死。
2. 减少 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 中,这里就有一个 业务表
和 一个 事务日志表
,而且要作为原子化提交,这里就波及到了事务操作。
2. 反对事务操作
因为有同时插入两张表的业务逻辑,免不了应用 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 的反对貌似也差不多了, 异步版的我就不在此封装啦。
四:总结
文章来源于工作中的点点滴滴,这也是我的即兴封装,大家要是有更好的封装代码,欢送交换,独乐乐不如众乐乐,本篇就说到这里啦,心愿对您有帮忙。