• 初学C#和MVC的一些心得,弯路,总结,还有教训(3)--Dapper


    经过一番深思熟虑,决定先用Dapper吧.....

    以下是我感觉比较有用的一些东西

    Dapper项目地址  https://github.com/StackExchange/dapper-dot-net

    初次接触Dapper,简单的范例  https://github.com/xliang/dapper-net-sample

    园子里树上的蜗牛 大大写的扩展系列 http://www.cnblogs.com/cyb331/p/3514555.html   文中的下载连接都失效了,这是扩展1.1的下载连接 http://pan.baidu.com/s/1bngXOAz

    如果以前写过sql语句,Dapper用起来算是比较顺手的,基本上还是以前那套 数据库连接,sql语句,记录集 的流程,只不过返回结果的时候就不用去记录集一个字段一个字段的读了了,结果可以直接绑定到模型上....

    Dapper项目自带了Contrib 和 Rainbow两个插件,我还没深入去了解,不知道还有没有什么高级功能,暂且当成语法糖吧,随着学习的深入,可能会有更深的理解

    从Dapper.net sample 可以看到直接用Dapper和两个插件完成增删改查的区别,如果你也是新手,没接触过,建议把这个范例下回去看看,基本大概就明白了

    我这里就简单的把插入数据的代码拿出来,简单做个比较

    Dapper直接插入一条数据是这样的

                    var supplier = new Supplier()
                    {
                        Address = "10 Main Street",
                        CompanyName = "DEF Corporation"
                    };
    
                    sqlConnection.Execute(
                                        @"
                                           insert Suppliers(CompanyName, Address)
                                           values (@CompanyName, @Address)
                                        ",
                        supplier); 

    Contrib插件是这样的

                    var supplier = new Supplier()
                                       {
                                           Address = "10 Main Street",
                                           CompanyName = "ABC Corporation"
                                       };
    
                    var supplierId = sqlConnection.Insert<Supplier>(supplier); 

    Rainbow是这样的

                    int? supplierId = db.Suppliers.Insert(new
                                                              {
                                                                  CompanyName = Guid.NewGuid().ToString()
    
                                                              });

    感觉上Rainbow在用的时候跟EF的流程差不多,定义Database的时候要把表名和实体(模型)都定义好...感觉这样用就不如直接去用EF了....所以,我的代码里面大都用了Contrib来做

        public class NorthwindDatabase : Database<NorthwindDatabase>
        {
            public Table<Supplier> Suppliers { get; set;  } 
        }

    另外还要介绍就是 树上的蜗牛 写的扩展,看了一下介绍和扩展的内容,感觉以后有些地方可能会用的上,于是也都导入到解决方案里面了...

    这里要说明一下的是,蜗牛大大写的DapperEx是从DbBase扩展的,而Contrib是从DbConnecttion扩展的....

    为了优化性能,实现一个请求使用一个数据库连接,所以我写了一个DBFactory,用CallContext来存储数据库连接实例,有关CallContext的说明,请msdn或者google,做为初学者,暂时只知道这样做可以就行了...

    代码如下

        public static class DBFactory
        {
    
            /// <summary>
            /// 获取主数据库连接
            /// </summary>
            /// <returns></returns>
            public static DbBase GetSDDB()
            {
                DbBase db = CallContext.GetData("SDDB") as DbBase;
                if (db == null)
                {
                    db = new DbBase("SdConnection");
                    CallContext.SetData("SDDB", db);
                }
                return db;
            }
    
    
        }
    View Code

    需要数据库的时候这样写就可以了

        public class ServiceBase
        {
           protected DbBase DB;
           public ServiceBase()
            {
                //取数据库对象
                DB = DBFactory.GetSDDB();
            }
        }

    具体操作的时候这样就OK了

            public IEnumerable<StaffUser> GetUserAll(int page = 0, int rows = 0)
            {
                IEnumerable<StaffUser> m = DB.DbConnecttion.Query<StaffUser>(@"
                Select *  
                From StaffUser
                order by IsEnable Desc,ID" + SDUtility.GetPageSql(page, rows));
                return m;
            }

    Contrib插件有一个地方很别扭,就是在增删改查的时候,会把Model后面加个s当作表名,这个行为也是学EF,不过人家EF更智能的一点是,会自动把英文单词转换为正确的复数形式,而Contrib就是简单粗暴的在后面加了个s,让我感觉很是不爽,于是乎就粗暴的打开源代码,找到 GetTableName 函数,改之......这样我model什么名字跟数据库的表名相同就OK了....

    当时打算用蜗牛大大写的DapperEx,还有一个原因就是原生的dapper不提供翻页支持....这个也让人很是恼火,以前用mssql的时候最恼火的也是这个....不如用mysql,直接limit xx,yy 就行了....

    不过后来偶然查资料得知,mssql2012的ORDER BY 语句支持用 offset_fetch 来实现翻页(控制返回的行数) ,微软的msdn里有代码示例 http://msdn.microsoft.com/zh-cn/library/ms188385(v=sql.110).aspx#Offset

    所以 SDUtility.GetPageSql 里的代码就简单的许多,直接拼接出 offset_fetch 语句即可

            public static string GetPageSql(int page = 0, int rows = 0)
            {
                page = page - 1;
                if (page < 0) page = 0;
                if (rows < 0) rows = 0;
                if (page==0 && rows==0)
                return "";
    
                return string.Format(" OFFSET {0} ROWS FETCH NEXT {1} ROWS ONLY",page*rows,rows);
            }

    有了dapper,Contrib还有offset_fetch,再加上高大上sql语句,哦,对了,还有功不可没的Json.net,平时大部分的需求都可以满足了.....

    复杂的表关系查询直接一句sql搞定,再也不像EF那样绞尽脑汁的去设计实体了....而且用dynimic和Json.net返回数据,也可以省下很多model,想想还真是有点小鸡冻,吼吼~~~

    再就是还有一个可能在实际使用中可能遇到的问题...就是更新和查询的时候,我们有可能只需要model的其中几个属性(字段),我是这样解决的....

    在model里面加了个方法,根据不同的操作需要,返回需要的字段,比如,在更新用户信息的时候,,有些字段是不能动的特别用来加密密码的salt字段,一旦生成了就不能动了...

    所以我的model会这样写,由于刚接触C#,完全面向对象的思想还没有深入,我也不知道这种写法会不会违反什么编程原则,总之,先凑合能用就行....如果你有更好的方法,也欢迎交流学习......

    GetUpdatePart用户返回编辑用户时需要更新的字段(登录次数,最后登录时间,还有salt字段要避开)
    PrepareToUpdate 是在更新数据之前调用一下,传回原来数据库中的数据做对比,看看密码是否更改了,如果密码改了,就根据salt重新加密一下
    PrepareToInsert 是在插入新数据之前调用,先生成一个salt,然后再对密码进行加密,同时把登录次数,最后登录时间赋值一下,防止出现null
        public class StaffUser
        {
            [Key] //For Contrib
            [Id(CheckAutoId = true)]//for Ex
            public int ID { get; set; }
            public string UserCode { get; set; }
            public string UserName { get; set; }
            public string Nick { get; set; }
            public string Password { get; set; }
            public byte IsEnable {get; set; }
            public int LoginCount { get; set; }
            public DateTime LastLogin { get; set; }
            public string Description { get; set; }
            public string salt { get; set; }
    
    
            //只更新部分字段
            public string[] GetUpdatePart()
            {
                return new string[] { "UserCode", "UserName", "Nick", "Password", "IsEnable", "Description" };
            }
            //更新前准备
            public string PrepareToUpdate(StaffUser Old)
            {
                //检查密码是否被修改
                if (Old.Password!=this.Password)
                {
                    //重新生成密码
                    this.Password = SDUtility.MD5(SDUtility.MD5(this.Password) + Old.salt);
                }
                return "";
            }
    
            //添加前准备
            public string PrepareToInsert()
            {
                //生成盐和密码
                this.salt = SDUtility.GetSalt();
                this.Password = SDUtility.MD5(SDUtility.MD5(this.Password) + this.salt);
                LoginCount = 0;
                LastLogin = DateTime.Now;
                return ""; 
            }
    
        }

    这样,我在插入新数据的时候就可以用 Contrib 了

            private string DoUserInsert(StaffUser row)
            {
                try
                {
                    //为插入做准备
                    row.PrepareToInsert();
                    DB.DbConnecttion.Insert<StaffUser>(row);
                    return "";
                }
                catch (Exception ex)
                {
                    return row.ID + row.UserName + "插入失败! " + ex.Message;
                }
            }

    为了用Contrib在update的时候实现只更新部分字段,所以继续对 SqlMapperExtensions 进行改造,增加了一个 UpdatePart 方法,就是把原来的Update方法里更新所有字段改成只更新参数传入的字段

       public static bool UpdatePart<T>(this IDbConnection connection, T entityToUpdate,string[] UpdateProps , IDbTransaction transaction = null, int? commandTimeout = null) where T : class
            {
                var proxy = entityToUpdate as IProxy;
                if (proxy != null)
                {
                    if (!proxy.IsDirty) return false;
                }
    
                var type = typeof(T);
    
                var keyProperties = KeyPropertiesCache(type);
                if (!keyProperties.Any())
                    throw new ArgumentException("Entity must have at least one [Key] property");
    
                var name = GetTableName(type);
    
                var sb = new StringBuilder();
                sb.AppendFormat("update {0} set ", name);
    
                var allProperties = TypePropertiesCache(type);
                var computedProperties = ComputedPropertiesCache(type);
                //var nonIdProps = allProperties.Except(keyProperties.Union(computedProperties));
    
                //只更新指定字段
                for (var i = 0; i < UpdateProps.Length; i++)
                {
                    var property = UpdateProps[i];
                    sb.AppendFormat("{0} = @{1}", property, property);
                    if (i < UpdateProps.Length - 1)
                        sb.AppendFormat(", ");
                }
                sb.Append(" where ");
                for (var i = 0; i < keyProperties.Count(); i++)
                {
                    var property = keyProperties.ElementAt(i);
                    sb.AppendFormat("{0} = @{1}", property.Name, property.Name);
                    if (i < keyProperties.Count() - 1)
                        sb.AppendFormat(" and ");
                }
                var updated = connection.Execute(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction);
                return updated > 0;
            }
    View Code

    然后更新数据的时候这样使用...

    private string DoUserUpdate(StaffUser row)
            {
                try
                {
                    //取出原来数据
                    var old = DB.DbConnecttion.Get<StaffUser>(row.ID);
                    //为更新做准备
                    row.PrepareToUpdate(old);
                    //只更新部分字段
                    DB.DbConnecttion.UpdatePart<StaffUser>(row,row.GetUpdatePart());
                    return "";
                }
                catch (Exception ex)
                {
                    return row.ID + row.UserName + "更新失败! " + ex.Message;
                }
    
            }
  • 相关阅读:
    2
    作业5
    实验十
    作业 5 指针应用
    九九乘法表
    实验七(课堂练习)
    实验六 数组 (2)
    实验六 数组
    课堂实验5(求从m到n之间(包括m和n)所有素数的和)
    课堂实验5-2
  • 原文地址:https://www.cnblogs.com/ssboy/p/3978339.html
Copyright © 2020-2023  润新知