zoukankan      html  css  js  c++  java
  • 用了Dapper之后就不要再见到SqlConnection咯

    一、背景

    前几天看公司一个新项目的底层使用了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中,这里就有一个 业务表 和 一个 事务日志表,而且要作为原子化提交,这里就涉及到了事务操作。

    三、支持事务操作

    因为有同时插入两张表的业务逻辑,免不了使用 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 的支持貌似也差不多了,异步版的我就不在此封装啦。

    四、总结

    文章来源于工作中的点点滴滴,这也是我的即兴封装,大家要是有更好的封装代码,欢迎交流,独乐乐不如众乐乐,本篇就说到这里啦,希望对您有帮助。


    来源:DotNet

  • 相关阅读:
    Oracle 12C ORA-65096: 公用用户名或角色名无效
    一张图记住PMP十大只是领域
    MAC系统升级后APACHE/MYSQL相关问题解决
    Mac配置Apache
    Android源码下载方法
    GIT 远程操作详解
    GIT 配置及常用命令
    安装Oracle-Redhat 5.4 64位
    近期工作计划
    新的起点
  • 原文地址:https://www.cnblogs.com/star8521/p/13473464.html
Copyright © 2011-2022 走看看