ORM开发之解析lambda实现完整查询(附测试例子) - hubro - 博客园

mikel阅读(693)

来源: ORM开发之解析lambda实现完整查询(附测试例子) – hubro – 博客园

上次讲解了怎么解析匿名对象(ORM开发之解析lambda实现group查询),这次来实现解析二元运算,完成基本条件语法

先看一个表达式

1
query.Where(b => b.Number == 10&&b.Id<20);

表达式结构

一个运算符表示一个表达式,因此,此表达式实际上包含两个子表达式 b.Number==10 和b.Id<20 他们的关系为And

看一个子表达式 b.Number==10
按运算符为位置,左边为左操作数,右边为右操作数

以And操作符来看,b.Number==10也为左操作数,b.Id<20为右操作数

再增加其它条件时,也是同样的道理

那么我们解析将一个子表达式 b.Number==10 转换为SQL逻辑,则需要:

  1. 取出左表达式对应的字段名称 Number
  2. 取出运算符 =
  3. 取出右表达式的值 10

表达式类型

由上可以看出,表达式分左边和右边,左右两边也可是子表达式,它们形成一个表达式树,基类型都为System.Linq.Expressions.Expression

具体类型大致按下面划分为:

  1. BinaryExpression 表示包含二元运算符的表达式。 可以理解为一个子表达式,如 b.Number>10
  2. MemberExpression 表示访问字段或属性。 如 b.Number
  3. NewArrayExpression 表示创建新数组并可能初始化该新数组的元素。
  4. MethodCallExpression 表示对静态方法或实例方法的调用 如 b.Name.Contains(“123”)
  5. ConstantExpression 表示具有常量值的表达式 如 b.Name=”hubro”
  6. UnaryExpression 表示包含一元运算符的表达式

因此,需要根据不同的类型解析不同的表达式

开始解析

拆分表达式树

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
/// <summary>
        /// 拆分表达式树
        /// </summary>
        /// <param name="left"></param>
        /// <param name="right"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public string BinaryExpressionHandler(Expression left, Expression right, ExpressionType type)
        {
            StringBuilder sb = new StringBuilder();
            sb.Append("(");
            string needParKey = "=,>,<,>=,<=,<>";
            string leftPar = RouteExpressionHandler(left);//获取左边
            string typeStr = ExpressionTypeCast(type);//转换运算符
            var isRight = needParKey.IndexOf(typeStr) > -1;//用以区分是解析左边的名称还是右边的值
            string rightPar = RouteExpressionHandler(right, isRight);//获取右边
            string appendLeft = leftPar;
            sb.Append(appendLeft);//字段名称
           
            if (rightPar.ToUpper() == "NULL")
            {
                if (typeStr == "=")
                    rightPar = " IS NULL ";
                else if (typeStr == "<>")
                    rightPar = " IS NOT NULL ";
            }
            else
            {
                sb.Append(typeStr);
            }
            sb.Append(rightPar);
            sb.Append(")");
            return sb.ToString();
        }

解析表达式

表达式树也会在这里处理,形成递归调用,当表达式是MemberExpression时,为了区分是左边的属性名还是右边的属性值,加了isRight进行区分

当是MethodCallExpression时,如果是左边,则需要进行解析(这里没有实现),右边只需要执行方法结果即可

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
/// <summary>
        /// 解析表达式
        /// </summary>
        /// <param name="exp"></param>
        /// <param name="isRight"></param>
        /// <returns></returns>
        public string RouteExpressionHandler(Expression exp, bool isRight = false)
        {
            if (exp is BinaryExpression)
            {
                BinaryExpression be = (BinaryExpression)exp;
                //重新拆分树,形成递归
                return BinaryExpressionHandler(be.Left, be.Right, be.NodeType);
            }
            else if (exp is MemberExpression)
            {
                MemberExpression mExp = (MemberExpression)exp;
                if (isRight)//按表达式右边值
                {
                    var obj = Expression.Lambda(mExp).Compile().DynamicInvoke();
                    if (obj is Enum)
                    {
                        obj = (int)obj;
                    }
                    return obj + "";
                }
                return mExp.Member.Name;//按左边的名称
            }
            else if (exp is NewArrayExpression)
            {
                #region 数组
                NewArrayExpression naExp = (NewArrayExpression)exp;
                StringBuilder sb = new StringBuilder();
                foreach (Expression expression in naExp.Expressions)
                {
                    sb.AppendFormat(",{0}", RouteExpressionHandler(expression));
                }
                return sb.Length == 0 ? "" : sb.Remove(0, 1).ToString();
                #endregion
            }
            else if (exp is MethodCallExpression)
            {
                if (isRight)
                {
                    return Expression.Lambda(exp).Compile().DynamicInvoke() + "";
                }
                //在这里解析方法
                throw new Exception("暂不支持");
            }
            else if (exp is ConstantExpression)
            {
                #region 常量
                ConstantExpression cExp = (ConstantExpression)exp;
                if (cExp.Value == null)
                    return "null";
                else
                {
                    return cExp.Value.ToString();
                }
                #endregion
            }
            else if (exp is UnaryExpression)
            {
                UnaryExpression ue = ((UnaryExpression)exp);
                return RouteExpressionHandler(ue.Operand, isRight);
            }
            return null;
        }

转换运算符

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
public string ExpressionTypeCast(ExpressionType expType)
        {
            switch (expType)
            {
                case ExpressionType.And:
                    return "&";
                case ExpressionType.AndAlso:
                    return " AND ";
                case ExpressionType.Equal:
                    return "=";
                case ExpressionType.GreaterThan:
                    return ">";
                case ExpressionType.GreaterThanOrEqual:
                    return ">=";
                case ExpressionType.LessThan:
                    return "<";
                case ExpressionType.LessThanOrEqual:
                    return "<=";
                case ExpressionType.NotEqual:
                    return "<>";
                case ExpressionType.Or:
                    return "|";
                case ExpressionType.OrElse:
                    return " OR ";
                case ExpressionType.Add:
                case ExpressionType.AddChecked:
                    return "+";
                case ExpressionType.Subtract:
                case ExpressionType.SubtractChecked:
                    return "-";
                case ExpressionType.Divide:
                    return "/";
                case ExpressionType.Multiply:
                case ExpressionType.MultiplyChecked:
                    return "*";
                default:
                    throw new InvalidCastException("不支持的运算符");
            }
        }

获取解析值

1
2
3
4
5
6
7
8
9
internal string FormatExpression(Expression<Func<T, bool>> expression)
        {
            string condition;
            var visitor = new ExpressionVisitor();
            if (expression == null)
                return "";
            condition = visitor.RouteExpressionHandler(expression.Body);
            return condition;
        }

拼接完整的SQL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public string GetQuery()
        {
            string where = Condition;
            where string.IsNullOrEmpty(where) ? " 1=1 " where;
            #region group判断
            if (groupFields.Count > 0)
            {
                where += " group by ";
                foreach (var item in groupFields)
                {
                    where += item + ",";
                }
                where where.Substring(0, where.Length - 1);
            }
            #endregion
            string tableName = typeof(T).Name;
            string fileds = string.Join(",", queryFields);
            var part = string.Format("select {0} from {1}  where {2}", fileds, tableName, where);
            return part;
        }

运行输出

1
2
3
4
5
6
7
8
9
var query = new LambdaQuery<Product>();
            query.Select(b => new { b.BarCode, b.ProductName, total = b.BarCode.COUNT() });
            query.GroupBy(b => new { b.BarCode, b.ProductName });
            query.Where(b => b.ProductName == "ddd");
            query.Where(b => b.Number == 10);
            query.Where(b => b.Number == new aa().bb);//测试获取对象参数
            query.OrderBy(b => b.BarCode.COUNT(), true);
            Console.Write(query.GetQuery());

这样,一般查询就能用lambda来表示了,但是一些SQL函数,是没法表示的,和之前说的一样,可以用扩展方法解决

上面上解析方法调用表达式里,解析即可,解析方法比较复杂,就不在这里写了

1
2
3
4
5
6
7
8
9
else if (exp is MethodCallExpression)
            {
                if (isRight)
                {
                    return Expression.Lambda(exp).Compile().DynamicInvoke() + "";
                }
                //在这里解析方法
                throw new Exception("暂不支持");
            }

测试例子下载 http://files.cnblogs.com/files/hubro/LambdaQueryTest2.rar

c# 轻量级 ORM 框架 之 Model解析 (四) - 老张一笑 - 博客园

mikel阅读(710)

来源: c# 轻量级 ORM 框架 之 Model解析 (四) – 老张一笑 – 博客园

关于orm框架设计,还有必要说的或许就是Model解析了,也是重要的一个环节,在实现上还是相对比较简单的.

Model解析,主要用到的技术是反射了,即:把类的属性与表的字段做映射. 把自己的设计及实现思路写出来也希望能有人给很好的优化建议,同时也给新手一点启发吧.

首先先给Model属性定义特性,先普及一下”特性”的概念和为什么用特性(Attribute).

简单来说,特性是给一个类,或方法,或属性 打上一个标记(或者叫附加信息),具体理解还是看例子比较好吧,

在做类与表之间映射时,我们需要知道某字段的是什么类型,长度,是否主键,自增长,非空,等信息,最简单较直观的方法或许就是特性(Attribute)了,

首先我们定义一个特性,它就是一个类而已,它必须继承自Attribute,我所写的orm比较轻量级,仅几个比较关键属性,

代码如下:


复制代码
public class ModelAttribute : Attribute
    {
        /// <summary>
        /// 是否主键
        /// </summary>
        public bool IsPrimaryKey  { set; get; }
        /// <summary>
        /// 主键是否自动增长
        /// </summary>
        public bool IsIdentity { set; get; }
        /// <summary>
        /// 是否非空字段
        /// </summary>
        public bool IsNotNull { set; get; }
        /// <summary>
        /// 列名
        /// </summary>
        public string ColumnName { set; get; }
    }
复制代码

下面是一个实体类使用特性的例子,它指明了Id的列名是:”Id”,不允许为空的,是自增长的,是主键:

复制代码
public class Test1 : ModelBase
    {
        [ModelAttribute(IsPrimaryKey = true, IsIdentity = true, IsNotNull = false, ColumnName = "Id")]
        public int Id { set; get; }
        public string Name { set; get; }
        public string Age { set; get; }
        public string Remark { set; get; }
    }
复制代码

下面是通过反射把Model特性解析出来,先把核心代码贴出来:

复制代码
     /// <summary>
        /// 通过解析获得Model的对象的参数,Key:为类的属性名
        /// </summary>
        /// <param name="model">model对象</param>
        /// <returns>返回model参数</returns>
        protected override Dictionary<string, ModelAttribute> GetModelParam<TModel>()
        {
            var list = new Dictionary<string, ModelAttribute>();
            PropertyInfo[] pros = ReflectionHelper.GetPropertyInfo<TModel>();
            foreach (PropertyInfo item in pros)
            {
                var attr = ReflectionHelper.GetCustomAttribute<ModelAttribute>(item);
                if (attr == null)
                {
                    //如果实体没定义属性则创建一个新的
                    attr = new ModelAttribute();
                    attr.ColumnName = item.Name;
                }
                else
                {
                    //如果列名没有赋值,则将列名定义和属性名一样的值
                    if (string.IsNullOrEmpty(attr.ColumnName))
                    {
                        attr.ColumnName = item.Name;
                    }                    
                }
                list.Add(item.Name, attr);
            }
            return list;
        }
复制代码

因考虑反射应该是共同方法,不仅限于Model解析,所以把反射相关的方法提出来了,以下是根据”类型T”获取自定义属性的两个方法:

复制代码
        /// <summary>
        /// 获得指定成员的特性对象
        /// </summary>
        /// <typeparam name="T">要获取属性的类型</typeparam>
        /// <param name="pInfo">属性原型</param>
        /// <returns>返回T对象</returns>
        public static T GetCustomAttribute<T>(PropertyInfo pInfo) where T : Attribute, new()
        {
            Type attributeType = typeof(T);
            Attribute attrObj = Attribute.GetCustomAttribute(pInfo, attributeType);
            T rAttrObj = attrObj as T;
            return rAttrObj;
        }
复制代码

复制代码
        /// <summary>
        /// 获得对象的所有公共属性信息
        /// </summary>
        /// <typeparam name="T">类型</typeparam>
        /// <param name="obj">获得的对象</param>
        /// <returns>返回属性信息</returns>
        public static PropertyInfo[] GetPropertyInfo<T>() where T : class
        {
            Type t = typeof(T);
            PropertyInfo[] proInfo = t.GetProperties();
            return proInfo;
        }
复制代码

解析特性我们不需要知道该类的具体实例,所以这里用了泛型,只需要知道Model类型即可,我的框架仅限于类的属性,这里只获取属性的”特性对象”.

返回类型Dictionary<string,ModelAttribute> Key:为属性名,ModelAttribute 对象,

到这里解析的实现其实就完成,后面我又做了一些优化,我们想到反射时通常会联想到效率问题,而且既然是解析一个类的特性,那么我们并不关心它的实例对象,

这里把解析出来的对象放到了缓存,即:只有第一次对该类进行反射,以后都是直接访问缓存数据.

解析Model是一个类,那么需要做到全局缓存,我这里用到了一个静态变量,该变量是不允许被外部更改的,所以设置为私有的了.

代码如下:

复制代码
     static object _LockObj1 = new object();
        static object _LockObj2 = new object();

        /// <summary>
        /// 实体类缓存,静态变量是保存为了减少反射次数
        /// </summary>
        static Dictionary<Type, Dictionary<string, ModelAttribute>> _ModelAttributeCache;
        /// <summary>
        /// 实体类缓存,静态变量是保存为了减少反射次数
        /// </summary>
        protected Dictionary<Type, Dictionary<string, ModelAttribute>> ModelAttributeCache
        {
            get
            {
                if (_ModelAttributeCache == null)
                {
                    lock (_LockObj1)
                    {
                        if (_ModelAttributeCache == null)
                        {
                            _ModelAttributeCache = new Dictionary<Type, Dictionary<string, ModelAttribute>>();
                        }
                    }
                }
                return _ModelAttributeCache;
            }
        }
        /// <summary>
        /// 获取Model的属性对象,获取第一次后会放入一个缓存列表中
        /// 即只反射一次
        /// </summary>
        public Dictionary<string, ModelAttribute> GetModelAttribute<T>() where T : ModelBase, new()
        {
            Type t = typeof(T);
            if (!ModelAttributeCache.ContainsKey(t))
            {
                lock (_LockObj2)
                {
                    if (!ModelAttributeCache.ContainsKey(t))
                    {
                        var attrs = GetModelParam<T>();
                        ModelAttributeCache.Add(t, attrs);
                    }
                }
            }
            return ModelAttributeCache[t];
        }
复制代码

这里缓存列表为: Dictionary<Type, Dictionary<string, ModelAttribute>> ,Type即Model类的类型.

解释一下加LockObj的意义,

我先声明一下,这个orm框架虽然比较轻量级,但我也不是共享的一个设计阶段或者或测试阶段的代码,也是经过几个小项目使用磨合过的.

_LockObj 是在一次多线程操作时发现的bug,当多个线程访问一个”全局对象”时,不加锁会访问冲突的问题.

Model解析类的路径:ZhCun.Framework.Common.Models.TableModel

下载了代码的可以去看下具体实现的详细方法.

在设计DalBase  时考虑了它应依赖抽象的理念,虽然没有想好关于Model解析除了反射还是否会有其它方法,但还是把它定义成了抽象.

 

到这已经完成了Model解析的功能.会再生成SQL语句的时候用到它.

有了以下方法示例,估计SQL文的生成就能实现了吧.

复制代码
       //得到Model对象(第一次会反射,再次调用时是从缓存获取)
            Dictionary<string, ModelAttribute> modelAttr = _ModelAnaly.GetModelAttribute<T>();
            //key:字段名(属性名)
            foreach (string item in modelAttr.Keys)
            {
                //得到列名(如果特性没有指定ColumnName值,则与属性名一样)
                string colName = modelAttr[item].ColumnName;
                //是否字增长
                bool isIdentity = modelAttr[item].IsIdentity;
                //是否主键
                bool isPrimaryKey = modelAttr[item].IsPrimaryKey;
            }
复制代码

关于Model解析类的实现 相对设计来说比较简单.

如果有大神有啥好的建议,或有什么不足,希望能 探讨,指正 .

c# 轻量级 ORM 框架 之 DBHelper 实现 (三) - 老张一笑 - 博客园

mikel阅读(770)

来源: c# 轻量级 ORM 框架 之 DBHelper 实现 (三) – 老张一笑 – 博客园

周末了比较清闲,把自己的orm框架整理了下,开源了.

已经做出来的东西通常感觉有些简单,一些新手或许听到”框架”一类的词觉得有些”高深”,简单来说orm就是把ado的封装.

在介绍这个框架的第一篇博文,已经把DalBase介绍了一下设计思路,本篇的DBHelper对象也是给dalBase来用的,可以说框架的所有定义对象都是为了它.

这里起名叫DBHelper,因为我也是从写SQLHelper开始的,DBHelper只不过是所有类型对ado操作的各种方法的封装,所以本篇博文希望给C#新手,或是对ado.net认识比较模糊的有一些帮助.

首先DBHelper定义是个抽象的,因为我不知道DalBase 到底要访问哪种数据库,但我知道不管哪中数据库都会有链接字符串,那我就要求,必须要有连接字符串.

public DbHelperBase(string connStr)
        {
            _ConnStr = connStr;
        }

 

DbHelper是个抽象的,那它的成员必然就不能有具体对象.

那就把ado.net常用的对象定义出来.(如果你是初学者,我的建议是 用到哪个对象再去定义,否则到后期自己都不知道定义它干啥呢)

复制代码
        protected abstract DbConnection DBConnectionObj { get; }
        protected abstract DbCommand DbCommandObj { get; }
        protected abstract DbDataAdapter DbDataAdapterObj { get; }
        protected DbTransaction DbTransObj;

        public DbConnection CurrentConnection
        {
            get
            {
                return DBConnectionObj;
            }
        }
复制代码

说明一下,为什么事务对象不是抽象的,因为事务对象始终是有DbConnection来创建的,我不用知道它具体是什么类型.

DbConnection 对象public也是为了扩展其它功能而存在的.

定义了是否事务的变量,所有ado操作都会判断当前是否处于事务的标记.

bool _IsTrans = false;

初学.net的朋友,应该都会有一个SQLHelper的类,我也曾经看过都大同小异,而且普遍都没有事务的实现,如果你中枪了,那么恭喜你,你即将会改变你SQLHelper的实现.

如下代码:

复制代码
     /// <summary>
        /// 执行一条指定命令类型(SQL语句或存储过程等)的SQL语句,返回所影响行数
        /// </summary>
        public int ExecNonQuery(string sqlText, CommandType cmdType, params DbParameter[] param)
        {
            using (SqlConnection conn = new SqlConnection(_ConnStr))
            {
                using (SqlCommand cmd = new SqlCommand(sqlText, conn))
                {
                    cmd.CommandType = cmdType;
                    if (param != null)
                        cmd.Parameters.AddRange(param);
                    conn.Open();
                    return cmd.ExecuteNonQuery();
                }
            }
        }
复制代码

以上代码看起来是没什么问题,但如果要启用事务的话想想是否可以实现呢?

如果说改造一下加上事务的代码就行的话

如下:

 SqlTransaction tran = conn.BeginTransaction();
 cmd.Transaction = tran;

这样显然是错的,因为我们往往多条执行语句通常是分多次调用 ExecNonQuery() 方法的.

这样的一个ado方法的封装显然是不合理的.

如果多个增删改查(注意:查询也可能在是事务里)的方法,在一个事务里,那么必须是一个数据库连接(Connection)

这就是为什么把那三个对象定义到外面的原因之一了,最重要的原因是我需要子类去重写它.

下面看看我的实现:

复制代码
        /// <summary>
        /// 打开连接,如果已经打开则什么都不执行了
        /// </summary>
        void OpenConnection()
        {
            if (DBConnectionObj.State != ConnectionState.Open)
            {
                DBConnectionObj.ConnectionString = _ConnStr;
                DBConnectionObj.Open();
            }
        }
复制代码
复制代码
        /// <summary>
        /// 给当前DbCommand对象赋值,并且OpenConnection();
        /// </summary>
        void SetCommandAndOpenConnect(string sqlText, CommandType cmdType, params DbParameter[] param)
        {
            //按说赋值Connection,CommandType,是不用多次赋值的
            DbCommandObj.CommandType = cmdType;
            DbCommandObj.Connection = DBConnectionObj;
            DbCommandObj.Parameters.Clear();
            if (param != null)
            {
                DbCommandObj.Parameters.AddRange(param);
            }
            DbCommandObj.CommandText = sqlText;
            OpenConnection();
        }
复制代码
复制代码
        /// <summary>
        /// 执行一条指定命令类型(SQL语句或存储过程等)的SQL语句,返回所影响行数
        /// </summary>
        public int ExecNonQuery(string sqlText, CommandType cmdType, params DbParameter[] param)
        {
            try
            {
                SetCommandAndOpenConnect(sqlText, cmdType, param);
                return DbCommandObj.ExecuteNonQuery();
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                CloseConnect();
            }
        }
复制代码

看到这三个方法或许对于初学者会感到迷茫了,没有看到任何有关事务的代码呢,兜个圈子,现在想象一下如果加事务的话,需要做什么?

我们先从理论上认识一下,事务处理的流程

1.指定事务是哪个Connection

2.Command的事务对象指定到该事务.

3.Open()

4.提交或回滚(我是能写汉字的地方绝不写拼音)

5.关闭连接.

继续说这几个方法,为什么定义SetCommandAndOpenConnect 和 OpenConnection 这两个方法,本着尽量减少重复代码的原则.仅此而已.

既然Connection和Command都已经定义到方法外了,那就是说我只要再执行Command.ExecuteNonQuery()方法前,给他们赋值就行了.

也就是开始事务只需要给这个两个对象赋值即可

事务的相关代码如下:

复制代码
        /// <summary>
        /// 开始执行事务
        /// </summary>
        public void TransStart()
        {
            OpenConnection();
            DbTransObj = DBConnectionObj.BeginTransaction();
            DbCommandObj.Transaction = DbTransObj;
            _IsTrans = true;
        }
        /// <summary>
        /// 事务提交
        /// </summary>
        public void TransCommit()
        {
            _IsTrans = false;
            DbTransObj.Commit();
            CloseConnect();
        }
        /// <summary>
        /// 事务回滚
        /// </summary>
        public void TransRollback()
        {
            _IsTrans = false;
            DbTransObj.Rollback();
            CloseConnect();
        }
复制代码

这就是事务的方法了.

最后一个CloseConnect()方法,差点把它遗忘了

复制代码
        /// <summary>
        /// 关闭连接,如果没有开始事务或连接打开时才关闭
        /// </summary>
        void CloseConnect()
        {
            if (!_IsTrans)
            {
                if (DBConnectionObj.State == ConnectionState.Open)
                {
                    DBConnectionObj.Close();
                    DBConnectionObj.Dispose();
                }
            }
        }
复制代码

当开始事务时,连接是不能关的.只有提交了或回滚了才会把当前连接断掉.

到这里其实DbHelper的设计基本完成,再贴一下关于查询的几个方法,和执行类似就不解释了.

复制代码
     /// <summary>
        /// 获得首行首列
        /// </summary>
        public object GetScalar(string sqlText, CommandType cmdType, params DbParameter[] param)
        {
            try
            {
                SetCommandAndOpenConnect(sqlText, cmdType, param);
                return DbCommandObj.ExecuteScalar();

            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                CloseConnect();
            }
        }
     /// <summary>
        /// 执行一条SQL语句返回DataSet对象
        /// </summary>
        public DataSet GetDataSet(string sqlText, CommandType cmdType, params DbParameter[] param)
        {
            try
            {
                SetCommandAndOpenConnect(sqlText, cmdType, param);
                DbDataAdapterObj.SelectCommand = DbCommandObj;
                DataSet ds = new DataSet();
                DbDataAdapterObj.Fill(ds);
                return ds;
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                CloseConnect();
            }
        }

     /// <summary>
        /// 获得DataReader对象
        /// </summary>
        public DbDataReader GetDataReader(string sqlText, CommandType cmdType, params DbParameter[] param)
        {
            try
            {
                SetCommandAndOpenConnect(sqlText, cmdType, param);
                CommandBehavior cmdBehavior = CommandBehavior.CloseConnection;
                if (_IsTrans)
                {
                    cmdBehavior = CommandBehavior.Default;
                }
                DbDataReader dbReader = DbCommandObj.ExecuteReader(cmdBehavior);
                return dbReader;
            }
            catch (Exception ex)
            {
                throw ex;
            }
            finally
            {
                //DataReader用dbReader对象来关闭
                //CloseConnect();
            }
        }
复制代码

这里需要注意的是关于返回DataReader对象时不能关闭Connect,和 cmdBehavior 的赋值.

 

好了,到这DBHelper的设计和核心代码已经全部实现了.

试想一下我现在要完成SQLServerHelper的实现需要做的是什么?

当然只要实现父类的那几个抽象属性就行了.

代码如下:

复制代码
 public class SQLHelper : DbHelperBase
    {
        public SQLHelper(string connStr)
            : base(connStr)
        { }

        SqlConnection _DBConnectionObj;
        SqlCommand _DbCommandObj;
        SqlDataAdapter _DbDataAdapterObj;

        protected override DbConnection DBConnectionObj
        {
            get
            {
                //SqlBulkCopy aa = new SqlBulkCopy(new SqlConnection());
                if (_DBConnectionObj == null)
                {
                    _DBConnectionObj = new SqlConnection(_ConnStr);
                }
                return _DBConnectionObj;
            }
        }

        protected override DbCommand DbCommandObj
        {
            get
            {
                if (_DbCommandObj == null)
                {
                    _DbCommandObj = new SqlCommand();
                }
                return _DbCommandObj;
            }
        }

        protected override DbDataAdapter DbDataAdapterObj
        {
            get
            {
                if (_DbDataAdapterObj == null)
                {
                    _DbDataAdapterObj = new SqlDataAdapter();
                }
                return _DbDataAdapterObj;
            }
        }

    }
复制代码

OracleHelper,oledbhelper,SQLiteHelper,就不贴代码了.

 

转载建议标明出处(我从来不强迫别人做我管不了的事).

整个框架的源码在介绍框架的第一篇博文里有.

欢迎吐槽,点赞,建议,批评,指正,抄袭.

c# 轻量级ORM框架 之 WhereHelper (二) - 老张一笑 - 博客园

mikel阅读(483)

来源: c# 轻量级ORM框架 之 WhereHelper (二) – 老张一笑 – 博客园

上篇文章发布了一些设计orm框架基层的和实现,有朋友提出WhereHelper是亮点,能被认可我表示高兴.

我就把WhereHelper设计思想和代码公开下.

WhereHelper 的概念就是再拼接where 条件,为了能兼容各种数据库和参数化查询,故封装了该对象.

首先根据我的框架结构:

1.Common库

这里主要定义了,所有层都访问的类型及常用方法,因为是介绍WhereHelper的实现,对其它就不做详细解释了.

WhereHelper定义到这一层是想着UI会用到该查询,故把该类型的定义放到Common里

2.DataAccess

该层是操作数据库的一些方法封装.

在Common里的WhereHelper它是抽象的(其实完全可以是接口),且构造函数为

WhereHelper的实现类均放在DataAccess里,而且它的实现类构造函数都为 internal ,这个是为了实现只有我的Dal具体类(SQLDalBase,OracleDalBase,等等)知道它应该创建什么类型的具体WhereHelper对象,其实在DalBase里定义了WHereHelper的工厂.

不能让调用者直接创建.否则整个系统改变数据库时,就意味着要改变它创建的具体对象.

以下是WHereHelper的代码具体实现

复制代码
public abstract class WhereHelper
    {       
        #region 构造函数

        protected WhereHelper()
        {
            _WhereText = new StringBuilder();
            _ParamDict = new Dictionary<string, object>();
        }
        protected WhereHelper(string paramNameKey)
            : this()
        {
            _ParamNameKey = paramNameKey;
        }
        protected WhereHelper(WhereHelper wh)
            : this()
        {
            this._LastParamIndex = wh._LastParamIndex;
            this._ParamDict = wh._ParamDict;
            this._WhereText = wh._WhereText;
            this._ParamNameKey = wh._ParamNameKey;
        }

        #endregion

        #region 重写ToString

        public override string ToString()
        {
            return _WhereText.ToString();
        }

        #endregion

        #region 字段

        string _ParamNameKey;
        StringBuilder _WhereText;
        Dictionary<string, object> _ParamDict;
        int _LastParamIndex = -1;

        #endregion

        #region 属性

        /// <summary>
        /// 返回当前对象所有产品参数集合字典,key:参数数名(包含@),value:参数值
        /// </summary>
        public Dictionary<string, object> ParamDict
        {
            get
            {
                return _ParamDict;
            }
        }
        /// <summary>
        /// 返回当前生成where文本
        /// </summary>
        public StringBuilder WhereText
        {
            get
            {
                return _WhereText;
            }
        }

        #endregion

        #region 外部方法

        /// <summary>
        /// 增加查询参数,并输出参数名,参数名会根据LastParamIndex递增
        /// </summary>
        /// <param name="v">参数值</param>
        /// <returns>返回参数名</returns>
        protected string AddParam(object v)
        {
            _LastParamIndex++;
            string paramName = string.Format("@P_{1}{0}", _LastParamIndex, _ParamNameKey);
            _ParamDict.Add(paramName, v);
            return paramName;
        }
        /// <summary>
        /// 清除文本内容和参数内容
        /// </summary>
        public void Clear()
        {
            _ParamDict.Clear();
            _WhereText.Remove(0, _WhereText.Length);
        }
        /// <summary>
        /// 当前where文本增加字符(通常该值是 字段名 ),返回当前对象
        /// </summary>
        public virtual WhereHelper Add(string str)
        {
            this._WhereText.AppendFormat(" {0} ", str);
            return this;
        }
        /// <summary>
        /// 比较符,等于 "="
        /// </summary>
        public virtual WhereHelper Equal(object v)
        {
            this._WhereText.AppendFormat(" = {0}", AddParam(v));
            return this;
        }
        /// <summary>
        /// 比较符,不等于 "!="
        /// </summary>
        public virtual WhereHelper EqualNot(object v)
        {
            _WhereText.AppendFormat(" <> {0}", AddParam(v));
            return this;
        }
        /// <summary>
        /// 比较符,大于 ">"
        /// </summary>
        public virtual WhereHelper GreaterThan(object v)
        {
            this._WhereText.AppendFormat(" > {0}", AddParam(v));
            return this;
        }
        /// <summary>
        /// 比较符,小于
        /// </summary>
        public virtual WhereHelper LessThan(object v)
        {
            this._WhereText.AppendFormat(" < {0}", this.AddParam(v));
            return this;
        }
        /// <summary>
        /// 比较符,大于等于
        /// </summary>
        public virtual WhereHelper GreaterThenEqual(object v)
        {
            this._WhereText.AppendFormat(" >= {0}", this.AddParam(v));
            return this;
        }
        /// <summary>
        /// 比较符,小于等于
        /// </summary>
        public virtual WhereHelper LessThanEqual(object v)
        {
            this._WhereText.AppendFormat(" <= {0}", this.AddParam(v));
            return this;
        }
        /// <summary>
        /// 比较符,In,参数为某范围内的比较值
        /// </summary>
        public virtual WhereHelper In(params object[] inArgs)
        {
            this._WhereText.Append(" in (");
            for (int i = 0; i < inArgs.Length; i++)
            {
                _WhereText.Append(AddParam(inArgs[i]));
                if (i < inArgs.Length - 1)
                {
                    _WhereText.Append(",");
                }
            }
            this._WhereText.Append(" )");
            return this;
        }
        /// <summary>
        /// 比较符,NotIn
        /// </summary>
        public virtual WhereHelper NotIn(params object[] inArgs)
        {
            this._WhereText.Append(" not in (");
            for (int i = 0; i < inArgs.Length; i++)
            {
                _WhereText.Append(AddParam(inArgs[i]));
                if (i < inArgs.Length - 1)
                {
                    _WhereText.Append(",");
                }
            }
            this._WhereText.Append(" )");
            return this;
        }
        /// <summary>
        /// 比较符 left Like %{0}
        /// </summary>
        public virtual WhereHelper LikeLeft(string v)
        {
            string addParam = AddParam(string.Format("%{0}", v));
            this._WhereText.AppendFormat(" like {0}", addParam);
            return this;
        }
        /// <summary>
        /// 比较符 right like {0}%
        /// </summary>
        public virtual WhereHelper LikeRight(string v)
        {
            string addParam = AddParam(string.Format("{0}%", v));
            this._WhereText.AppendFormat(" like {0}", addParam);
            return this;
        }
        /// <summary>
        /// 比较符 full like
        /// </summary>
        public virtual WhereHelper LikeFull(string v)
        {
            string addParam = AddParam(string.Format("%{0}%", v));
            this._WhereText.AppendFormat(" like {0}", addParam);
            return this;
        }
        /// <summary>
        /// 增加一个左括号 "("
        /// </summary>
        public virtual WhereHelper BracketLeft()
        {
            this._WhereText.AppendFormat("(");
            return this;
        }
        /// <summary>
        /// 增加一个左括号"(",再加一个字符串(通常是一个字段)
        /// </summary>
        public virtual WhereHelper BracketLeft(string str)
        {
            this._WhereText.AppendFormat("(");
            this._WhereText.AppendFormat(str);
            return this;
        }
        /// <summary>
        /// 增加一个右括号 ")"
        /// </summary>
        public virtual WhereHelper BracketRight()
        {
            this._WhereText.Append(")");
            return this;
        }
        /// <summary>
        /// 增加连接符 "and"
        /// </summary>
        public virtual WhereHelper And()
        {
            And(string.Empty);
            return this;
        }
        /// <summary>
        /// 增加连接符 "and"
        /// </summary>
        public virtual WhereHelper And(string str)
        {
            if (this._WhereText.Length > 0)
            {
                this._WhereText.Append(" and ");
            }
            this._WhereText.Append(str);
            return this;
        }
        /// <summary>
        /// 增加连接符 "or"
        /// </summary>
        public virtual WhereHelper Or()
        {
            Or(string.Empty);
            return this;
        }
        /// <summary>
        /// 增加连接符"or",再加一个字符串(通常是一个字段)
        /// </summary>
        public virtual WhereHelper Or(string str)
        {
            if (this._WhereText.Length > 0)
            {
                this._WhereText.Append(" or ");
            }
            this._WhereText.Append(str);
            return this;
        }

        #endregion
    }
复制代码

代码实现比较简单.

这个抽象类有了具体实现,如果再使用非SQLServer数据库时报错了(SQL语法),那么就去DataAccess中找到它对应的具体类,去重写它的方法.

该类的所有public方法都返回了 this,也就是说,你”点”把它始终是一个对象.

拼接方式也非常灵活,可以直接拼写sql文(但不建议)它会给迁移数据库带来隐患.

至于参数的名字,只要你是一个WHereHleper对象,它是肯定不会重复的,如果你要拼接另外一个未知的WHereHelper到当前对象你可以用它的另一个构造(用到的几率很小)

举个例子吧:

sql where条件:  姓名=’张三’   and 年龄 > 20 and 年龄<30 and (性别=男 or 性别=女 )

代码如下:

 

复制代码
WhereHelper wh1 = _BllObj.CreateWhereHelper();
            wh1.Add("姓名")
               .Equal("张三")
               .And("年龄")
               .GreaterThan(20)
               .And("年龄")
               .LessThan(30)
               .And()
               .BracketLeft("性别")
               .Equal("男")
               .Or("性别")
               .BracketRight();
复制代码

为了格式好看,我把它每次”点”分成了一行(这个看起来确实有点像JQuery的感觉);

 

好了,这个实现大概先说这么多,如果有什么问题欢迎留言.

有人回复,我才有写下去的动力.

关于我的orm框架,第一篇,其实是实现的根本.

值的写出来或许还有DBHelper的实现,通常初学者都会写一个具体的如:SQL Server 数据的 SQLHelper ,当你写另外一个数据库支持的时候就会发现你的重复代码很多,就会想起我们亲爱的”面向对象”了.

c# 轻量级ORM框架 实现(一) - 老张一笑 - 博客园

mikel阅读(611)

来源: c# 轻量级ORM框架 实现(一) – 老张一笑 – 博客园

发布一个自己写的一个轻量级ORM框架,本框架设计期初基于三层架构.所以从命名上来看,了解三层的朋友会很好理解.

设计该框架的目的:不想重复的写增删改查,把精力放到功能实现上.

发布改框架的原因:希望给初学者一个参考,希望能给予好的建议,给自己一个展示机会.

在我开始之前,先说明一下,我对”软件工程学”概念东西几乎不通,最高文化程度:初二,所以不喜勿喷.

开始我的orm设计最底层

最底层的是一个DalBase,它是一个抽象的,实现了增删改查的基本操作.

它既然是一个抽象的,那么它的内部就不应该有任何具体成员.

它内部核心对象有:访问数据库的对象,生成SQL文的对象的抽象定义,创建SQL参数的抽象方法,where参数化查询对象的抽象定义.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/// <summary>
/// 访问数据的DBHelper对象
/// </summary>
public abstract DbHelperBase DBHelper { get; } //子类实现
/// <summary>
/// 获得生成SQL文本的对象
/// </summary>
protected internal abstract BuildSQL BuildSQLTextObj { get; }
/// <summary>
/// 获得数据参数对象
/// </summary>
protected internal abstract DbParameter GetDbParam(string paramName, object paramValue);
/// <summary>
/// 创建WhereHelper对象
/// </summary>
internal abstract WhereHelper CreateWhereHelper();   

如需扩展支持的数据库种类,只需要分别对这几个抽象对象进行具体代码的实现.

以下代码是增删改(查询相对来说比较复杂,单独放出来)

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
/// <summary>
   /// 执行sql语句返回所影响行数
   /// </summary>
   public virtual int ExecuteBySQL(string sqlText, Dictionary<stringobject> dbParams)
   {
       //执行sql语句的操作都由此方法实现
       DbParameter[] parameters = GetDbParam(dbParams);
       int rows = DBHelper.ExecNonQuery(sqlText, parameters);
       return rows;
   }
   /// <summary>
   /// 新增1条或多条
   /// </summary>
   public virtual int Add<TModel>(params TModel[] models) where TModel : ModelBase, new()
   {
       ThrowModelIsNullException(models);
       string sqlText;
       Dictionary<stringobject> dbParams;<br>       //这句话其实内部实现访问了 BuildSQLObj 的 InsertSQLTExtAndParam ,它生成sql语句和sql参数对象,把它又写成方法是为了让子类还可以对它进行重写,这个或许之前想多了,直接重写Add也是可以的.
       GenerateInsertSQLTextAndParam(out sqlText, out dbParams, models);
       int rows = ExecuteBySQL(sqlText, dbParams);
       return rows;
   }
   /// <summary>
   /// 更新一条或多条记录,根据sql条件,指定更新字段(sqlWhere为空,则按主键更新)
   /// </summary>
   public virtual int Update<TModel>(string[] fields, string sqlWhere, Dictionary<stringobject> dbParams, params TModel[] models) where TModel : ModelBase, new()
   {
       ThrowModelIsNullException(models);
       string sqlText;
       GenerateUpdateSQLTextAndParam(out sqlText, ref dbParams, sqlWhere, fields, models);
       int rows = ExecuteBySQL(sqlText, dbParams);
       return rows;
   }
   /// <summary>
   /// 更新一条或多条记录,根据sql条件,指定忽略更新的字段
   /// </summary>
   public virtual int UpdateByIgnoreField<TModel>(string[] ignoreFields, string sqlWhere, Dictionary<stringobject> dbParams, params TModel[] models) where TModel : ModelBase, new()
   {
       string[] allFields = BuildSQLTextObj.GetAllFields<TModel>();
       string[] updateFields = BuildSQLTextObj.RemoveFields(allFields, ignoreFields);
       return Update(updateFields, sqlWhere, dbParams, models);
   }
   /// <summary>
   /// 根据主键删除记录
   /// </summary>
   public virtual int Delete<TModel>(params object[] pkValues) where TModel : ModelBase, new()
   {
       string sqlText;
       Dictionary<stringobject> dbParams;
       if (pkValues == null || pkValues.Length < 0)
       {
           throw new DbDataException("删除操作时主键为空!");
       }
       GenerateDeleteSQLTextAndParam<TModel>(out sqlText, out dbParams, pkValues);
       int rows = ExecuteBySQL(sqlText, dbParams);
       return rows;
   }

查询提供了,3中方法,返回DataSet,返回List,返回DataReader,以及分页的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public virtual DataTable GetDataTable<TModel>(string sqlWhere, Dictionary<stringobject> dbParams, string[] orderFields, bool isDesc, params string[] selectFields) where TModel : ModelBase, new()
{
  string sqlText;
  GenerateSearchSQLTextAndParam<TModel>(sqlWhere, dbParams, orderFields, isDesc, out sqlText, selectFields);
  return GetDataTableBySQL(sqlText, dbParams);
}
public virtual DbDataReader GetDataReader<TModel>(string sqlWhere, Dictionary<stringobject> dbParams, string[] orderFields, bool isDesc, params string[] selectFields) where TModel : ModelBase, new()
{
  string sqlText;
  GenerateSearchSQLTextAndParam<TModel>(sqlWhere, dbParams, orderFields, isDesc, out sqlText, selectFields);
  return GetDataReaderBySQL(sqlText, dbParams);
}
public virtual List<TModel> GetList<TModel>(string sqlWhere, Dictionary<stringobject> dbParams, string[] orderFields, bool isDesc, params string[] selectFields) where TModel : ModelBase, new()
{
  DbDataReader dbReader = GetDataReader<TModel>(sqlWhere, dbParams, orderFields, isDesc, selectFields);
  List<TModel> list = GetListByDataReader<TModel>(dbReader);
  return list;
}

为什么有个DataReader方法呢,返回它有两个用处,1是把它转换成List,2因为DataSet的获取内部也是调用了DataReader方法,(通过反编译可以看到的)

因为DataReader 转换 List 要比 DataTable to List的效率要快;

DalBase的的基本功能其实就差不多了,下边来介绍下BLLbase的实现,BLLBase主要实现了dal方法的一些重载而已,

从字面意思来说,业务逻辑的基类,我这里定义了业务逻辑的规则,比如Insert前(后)的事件,查询前的事件,等等..

BLLBase里增删改其实和DalBase并无特别差别,就不介绍了.

主要说下Get的方法.

1
2
3
4
5
6
7
8
public virtual DataTable GetDataTable<TModel>(string[] selectFields, string[] orderFields, bool isDesc, WhereHelper dbWhereModel) where TModel : ModelBase, new()
        {
            StringBuilder sqlWhere = null;
            Dictionary<stringobject> dbParam = null;
            GetSqlWhereParam(ref sqlWhere, ref dbParam, dbWhereModel);
            return GetDataTable<TModel>(sqlWhere.ToString(), dbParam, orderFields, isDesc, selectFields);
        }

这是一个带where条件的查询,返回datatable,其它获取List,DataReader,方法都是一样的,WhereHelper这个类的创建,我自豪了很久,在下面将调用时会举例它的使用及实现.

举个测试例子:

1.创建一个WinForm程序,引用 ZhCun.Framework.Common 和ZhCunFramework.DataAccess

2.创建Models文件夹,分别建Test1.cs和Test2.cs ,这两个是表的映射.如下:

1
2
3
4
5
6
7
8
9
10
11
namespace ZhCun.Framework.WinTest.Models
{
    public class Test1 : ModelBase
    {
        [ModelAttribute(IsPrimaryKey = true, IsIdentity = true)]
        public int Id { setget; }
        public string Name { setget; }
        public string Age { setget; }
        public string Remark { setget; }
    }
}

映射的Model必须继承ModelBase,这就是为什么在DalBase里面加泛型约束的原因,其实ModelBase我并没有想好用它来实现什么,就当个限制条件吧.

另外 ModelAttribute 特性,指定该属性的映射数据库表的类型及其它规则,这里Id表示是一个自增长的主键.

3.创建一个BLLCommon 类,这个类名或许叫什么  XXXXServer ,或许更好一些,它继承了BLLBase并指定了连接字符串.

那两个表就手工建一下吧,连接字符串可以直接指定写死喽

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class BLLCommon : BLLBase
    {
        static string ConnStr
        {
            get
            {
                // "Data Source=192.168.0.55;Initial Catalog=aa;uid=sa;pwd=123";
                return Configurations.GetConnectString("Test");
            }
        }
        public BLLCommon()
            base(DatabaseTypeEnum.SQLServer, ConnStr)
        { }
}

BLLCommon指定了连接字符串和数据库类型,如果一个项目使用多个(或多种)数据库,可以创建多个BLLCommon,

BLLBase的所有方法都定义的虚方法,所以在这里可以重写你要改的东西,来完成业务逻辑的限制或约束.

如,我想要在增加Test1表数据时,Remark赋值’aa’

1
2
3
4
5
6
7
8
9
10
11
12
public override int Add<TModel>(params TModel[] models)
        {
            foreach (var item in models)
            {
                Test1 m = item as Test1;
                if (m != null)
                {
                    m.Remark = "aa";
                }
            }
            return base.Add<TModel>(models);
        }

 

下面是调用代码:

此代码实现了,新增和更新,及事务的使用方法.

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
BLLCommon _BllObj = new BLLCommon();
   private void btnAdd_Click(object sender, EventArgs e)
   {
       Test1 t1 = new Test1();
       Test2 t2 = new Test2();
       t1.Name = txtName.Text;
       t1.Age = txtAge.Text;
       t1.Remark = txtRemark.Text;
       t2.Name = txtName.Text;
       t2.Age = txtAge.Text;
       t2.Remark = txtRemark.Text;
       try
       {
           _BllObj.TransStart();
           _BllObj.Add(t2);
           _BllObj.Add(t1);
           var model = _BllObj.GetModel<Test1>(1);
           if (model != null)
           {
               model.Remark = "更新时间:" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss");
               _BllObj.Update(new string[] { "Remark" }, model);
           }
           _BllObj.TransCommit();
           MessageBox.Show("提交成功!");
       }
       catch (Exception ex)
       {
           _BllObj.TransRollback();
           MessageBox.Show(ex.Message);
       }
   }

 

带where查询的方法调用:

        WhereHelper wh1 = _BllObj.CreateWhereHelper();
        wh1.Add("Name").Equal("张三");
        List<Test1> list1 = _BllObj.GetList<Test1>(wh1);

WhereHelper对象为啥要用BLLObj来创建,这个解释一下,考虑到不同数据库的查询应该有不同的语法,把WhereHelper抽象出来,也是为了扩展.

引用BLLBase的时候指定了数据库,所以BLL是知道创建哪中数据库的WhereHelper的,所以用BLL对象来创建WhereHelper是最合适的,这样如果切换数据库不会受任何影响.

1
2
3
4
wh1.Add("字段1")
       .Equal(1)
       .And("字段2")
       .EqualNot(2);       

这个是收到JQuery的启发,来设计的类,每一个操作都返回了当前对象(即WhereHelper),也就是说,它可以无限的”点”下去.

使用它的最大好处是:你不用考虑参数名的重复,不用考虑换库的兼容性,操作起来是如此简单.

关于WHereHelper的使用及设计思想请看:  轻量级ORM框架 之 WhereHelper (二)

 

费劲的写了这么多,如果有人看有人有要求,或有什么建议,请留言.

 

昨天看了一篇关于程序员的文章,一个优秀的程序员6大特质,其中之一是:懂的分享.

把这套框架源码共享下  下载

下载说明:

本框架没有用于过任何商业用途(当然以后就不一定了)

    欢迎 指正,批评,优化,建议,抄袭及商业使用

共享的目的是为了优化一下框架,接收一下建议,了解下不足.

.NET(C#)有哪些主流的ORM框架,FreeSql,SqlSugar,Dapper,EF还是..._.NET技术 - UCloud云社区

mikel阅读(654)

来源: .NET(C#)有哪些主流的ORM框架,FreeSql,SqlSugar,Dapper,EF还是…_.NET技术 – UCloud云社区

前言

在以前的一篇文章中,为大家分享了《什么是ORM?为什么用ORM?浅析ORM的使用及利弊》。那么,在目前的.NET(C#)的世界里,有哪些主流的ORM,FreeSQLSQLSugar,Dapper,Entity Framework(EF)还是ServiceStack.OrmLite?或者是你还有更好的ORM推荐呢? 如果有的话,不防也一起分享给大家。

.NET(C#)主流ORM总揽

今天这篇文章分享几款收集的目前.NET(C#)中比较流行的ORM框架,比如(以下框架均为开源框架,托管于github上):

FreeSql (国内)

SqlSugar (国内)

Dos.ORM (国内)

Chloe (国内)

StackExchange/Dapper (国外)

Entity Framework (EF) (国外)

NHibernate (国外)

ServiceStack/ServiceStack.OrmLite (国外)

linq2db (国外)

Massive (国外)

PetaPoco (国外)

FreeSql

FreeSql是一个功能强大的NETStandard库,用于对象关系映射程序(O/RM),以便于开发人员能够使用 .NETStandard 对象来处理数据库,不必经常编写大部分数据访问代码。

特性

开源、免费。

CodeFirst 迁移。

DbFirst 从数据库导入实体类,支持三种模板生成器。

采用 ExpressionTree 高性能读取数据。

类型映射深入支持,比如pgsql的数组类型,堪称匠心制作。

支持丰富的表达式函数。

支持导航属性查询,和延时加载。

支持同步/异步数据库操作方法,丰富多彩的链式查询方法。

支持事务。

支持多种数据库,MySql/SQLServer/PostgreSQL/Oracle/Sqlite。

推荐等级:★★★★★

github:https://github.com/2881099/Fr…

SqlSugar

SqlSugar是国人开发者开发的一款基于.NET的ORM框架,是可以运行在.NET 4.+ & .NET CORE的高性能、轻量级 ORM框架,众多.NET框架中最容易使用的数据库访问技术。

特点:

开源、免费

国内开发者开发、维护;

支持.NET Core;

支持主流数据库,如:SQL Server,MySql,Oracle,Sqlite等;

维护更新及时

推荐等级:★★★★☆

PetaPoco

PetaPoco:轻量的POCO对象和数据库映射的ORM框架。

特点:

开源、免费

推荐等级:★★★★☆

linq2db

linq2db也是一款快速、轻量、类型安全的POCO对象和数据库映射的ORM框架。从构架上来说,linq2db是对比如:Dapper、PetaPoco这个的微ORM的进一步封装,但它不像Entity Framework那样笨重。它没有实现状态跟踪,需要自己处理实体的状态更改等。

推荐等级:★★★★☆

Dos.ORM

Dos.ORM(原Hxj.Data)于2009年发布,2015年正式开源。在开发过程中参考了NBear与MySoft,吸取了他们的一些精华,加入新思想,同时参考EF的Lambda语法进行大量扩展。该组件已在数百个成熟项目中应用。官方网站:http://ITdos.com/Dos/ORM/Inde…

特点:

开源、免费

上手简单,0学习成本。使用方便,按照sql书写习惯编写C#.NET代码。功能强大

高性能,接近手写Sql

体积小(不到150kb,仅一个dll)

完美支持Sql Server(2000至最新版),MySql,Oracle,Access,Sqlite等数据库

支持大量Lambda表达式写法,国产ORM支持度最高,开源中国ORM排行前三

不需要像NHibernate的XML配置,不需要像EF的各种数据库连接驱动

遵循MIT开源协议,除不允许改名,其它随意定制修改

推荐等级:★★★☆☆

ServiceStack.OrmLite

ServiceStack.OrmLite的目标是提供一种方便,无干扰,无配置的RDBMS无关类型的封装,与SQL保持高度的契合,展现直观的API,可以生成可预测的SQL。
ServiceStack.OrmLite的宗旨:Fast, Simple, Typed ORM for .NET

特点:

开源、收费(免费版只支持单个库10张表)

推荐等级:★★★☆☆

Entity Framework (EF)

ADO.NET Entity Framework 是微软以 ADO.NET 为基础所发展出来的对象关系对应 (O/R Mapping) 解决方案。该框架曾经为.NET Framework的一部分,但version 6之后从.NET Framework分离出来。

推荐等级:★★★☆☆

NHibernate

NHibernate是一个面向.NET环境的对象/关系数据库映射工具。对象/关系数据库映射(object/relational mapping,ORM)这个术语表示一种技术,用来把对象模型表示的对象映射到基于SQL的关系模型数据结构中去。

特点:

开源、免费

批量写入

批量读/多重查询特性(我理解是在说Future?)

批量的集合加载

带有lazy=”extra”的集合

集合过滤器和分页集合

二级缓存(实际上NH的二级缓存貌似也很简单?)

集成和扩展性

代码自动生成,减少代码和sql的开发量,使开发人员摆脱开sql,ado.net和事务,缓存等底层

推荐等级:★★★☆☆

Massive

Massive:小巧,动态的微ORM框架。

推荐等级:★★★☆☆

以上是笔者收集整理的部分目前.NET(C#)中流行的ORM框架。

如果你还有其他更好的ORM,欢迎留言,交流。

如果你觉得本文对你或者他人有帮助,请点个赞吧。

sql server在高并发状态下同时执行查询与更新操作时的死锁问题_ajianchina的博客-CSDN博客

mikel阅读(595)

来源: sql server在高并发状态下同时执行查询与更新操作时的死锁问题_ajianchina的博客-CSDN博客

最近在项目上线使用过程中使用SQLServer的时候发现在高并发情况下,频繁更新和频繁查询引发死锁。通常我们知道如果两个事务同时对一个表进行插入或修改数据,会发生在请求对表的X锁时,已经被对方持有了。由于得不到锁,后面的Commit无法执行,这样双方开始死锁。但是select语句和update语句同时执行,怎么会发生死锁呢?看完下面的分析,你会明白的…

首先看到代码中使用的查询的方法Select

  1. /// <summary>
  2. /// 根据学生ID查询教师信息。用于前台学生评分主页面显示
  3. /// </summary>
  4. /// <param name=”enTeacherCourseStudent”>教师课程学生关系实体:StudentID</param>
  5. public DataTable QueryTeacherByStudent(TeacherCourseStudentLinkEntity enTeacherCourseStudent)
  6. {
  7. //TODO:QueryTeacherByStudent string strSQL = “SELECT ID, CollegeTeacherID,CollegeTeacherName,TeacherID,TeacherCode,” +
  8. //”TeacherName,CourseID,CourseName,CourseTypeID,CourseTypeName,” +
  9. //”StudentID,StudentName,IsEvluation FROM TA_TeacherCourseStudentLink WITH(NOLOCK) ” +
  10. //”WHERE StudentID = @StudentID”;
  11. //根据学生ID查询该学生对哪些教师评分的SQL语句
  12. string strSql = “SELECT ID, CollegeTeacherID,CollegeTeacherName,TeacherID,TeacherCode,” +
  13. “TeacherName,CourseID,CourseName,CourseTypeID,CourseTypeName,” +
  14. “StudentID,StudentName,IsEvluation FROM TA_TeacherCourseStudentLink WITH(NOLOCK) “ +
  15. “WHERE StudentID = @StudentID”;
  16. //参数
  17. SqlParameter[] para = new SqlParameter[] {
  18. new SqlParameter(“@StudentID”,enTeacherCourseStudent.StudentID) //学生ID
  19. };
  20. //执行带参数的sql查询语句或存储过程
  21. DataTable dtStuTeacher = sqlHelper.ExecuteQuery(strSql, para, CommandType.Text);
  22. //返回查询结果
  23. return dtStuTeacher;
  24. }

更新方法

  1. /// <summary>
  2. /// 学生对教师评分完毕,是否评估由N变为Y
  3. /// </summary>
  4. /// <param name=”enTeacherCourseStudent”>教师课程学生关系实体:StudentID、TeacherID、CourseID</param>
  5. /// <return>是否修改成功,true成功,false失败</return>
  6. public Boolean EditIsEvaluation(TeacherCourseStudentLinkEntity enTeacherCourseStudent, SqlConnection sqlCon, SqlTransaction sqlTran)
  7. {
  8. //更改是否评估字段为”Y”的sql语句
  9. string strSql = “UPDATE TA_TeacherCourseStudentLink WITH(UPDLOCK) SET IsEvluation=’Y’ WHERE TeacherID=@TeacherID AND StudentID=@StudentID AND CourseID=@CourseID”;
  10. //参数
  11. SqlParameter[] paras = new SqlParameter[]{
  12. new SqlParameter(“@TeacherID”,enTeacherCourseStudent.TeacherID), //教师ID
  13. new SqlParameter(“@StudentID”,enTeacherCourseStudent.StudentID), //学生ID
  14. new SqlParameter(“@CourseID”,enTeacherCourseStudent.CourseID) //课程ID
  15. };
  16. //李社河添加2014年12月29日
  17. Boolean flagModify = false;
  18. try
  19. {
  20. //执行带参数的增删改sql语句或存储过程
  21. flagModify = sqlHelper.ExecNoSelect(strSql, paras, CommandType.Text, sqlCon, sqlTran);
  22. }
  23. catch (Exception e)
  24. {
  25. throw e;
  26. }
  27. //返回修改结果
  28. return flagModify;
  29. }

 

现在分析,在数据库系统中,死锁是指多个用户(进程)分别锁定了一个资源,并又试图请求锁定对方已经锁定的资源,这就产生了一个锁定请求环,导致多个用户(进程)都处于等待对方释放所锁定资源的状态。还有一种比较典型的死锁情况是当在一个数据库中时,有若干个长时间运行的事务执行并行的操作,当查询分析器处理一种非常复杂的查询例如连接查询时,那么由于不能控制处理的顺序,有可能发生死锁现象。

那么,什么导致了死锁?

现象图

通过查询SQLServer的事务日志视图,发生的错误日志视图知道是在高并发的情况下引发的update和select发生的死锁,接下来我们看例子;

  1. CREATE PROC p1 @p1 int AS
  2. SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
  3. GO
  4. CREATE PROC p2 @p1 int AS
  5. UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
  6. UPDATE t1 SET c2 = c2-1 WHERE c1 = @p1
  7. GO

p1没有insert,没有delete,没有update,只是一个select,p2才是update。那么,什么导致了死锁?

  1. 需要从事件日志中,看sql的死锁信息:
  2. Spid X is running this query (line 2 of proc [p1], inputbuffer “… EXEC p1 4 …”):
  3. SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
  4. Spid Y is running this query (line 2 of proc [p2], inputbuffer “EXEC p2 4”):
  5. UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
  6. The SELECT is waiting for a Shared KEY lock on index t1.cidx. The UPDATE holds a conflicting X lock.
  7. The UPDATE is waiting for an eXclusive KEY lock on index t1.idx1. The SELECT holds a conflicting S lock.

首先,我们看看p1的执行计划。怎么看呢?可以执行set statistics profile on,这句就可以了。下面是p1的执行计划

  1. SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
  2. |–Nested Loops(Inner Join, OUTER REFERENCES:([Uniq1002], [t1].[c1]))
  3. |–Index Seek(OBJECT:([t1].[idx1]), SEEK:([t1].[c2] >= [@p1] AND [t1].[c2] <= [@p1]+(1)) ORDERED FORWARD)
  4. |–Clustered Index Seek(OBJECT:([t1].[cidx]), SEEK:([t1].[c1]=[t1].[c1] AND [Uniq1002]=[Uniq1002]) LOOKUP ORDERED FORWARD)

我们看到了一个nested loops,第一行,利用索引t1.c2来进行seek,seek出来的那个rowid,在第二行中,用来通过聚集索引来查找整行的数据。这是什么?就是bookmark lookup啊!为什么?因为我们需要的c2、c3不能完全的被索引t1.c1带出来,所以需要书签查找。

好,我们接着看p2的执行计划。

  1. UPDATE t1 SET c2 = c2+1 WHERE c1 = @p1
  2. |–Clustered Index Update(OBJECT:([t1].[cidx]), OBJECT:([t1].[idx1]), SET:([t1].[c2] = [Expr1004]))
  3. |–Compute Scalar(DEFINE:([Expr1013]=[Expr1013]))
  4. |–Compute Scalar(DEFINE:([Expr1004]=[t1].[c2]+(1), [Expr1013]=CASE WHEN CASE WHEN …
  5. |–Top(ROWCOUNT est 0)
  6. |–Clustered Index Seek(OBJECT:([t1].[cidx]), SEEK:([t1].[c1]=[@p1]) ORDERED FORWARD)

通过聚集索引的seek找到了一行,然后开始更新。这里注意的是,update的时候,它会申请一个针对clustered index的X锁的。

实际上到这里,我们就明白了为什么update会对select产生死锁。update的时候,会申请一个针对clustered index的X锁,这样就阻塞住了(注意,不是死锁!)select里面最后的那个clustered index seek。死锁的另一半在哪里呢?注意我们的select语句,c2存在于索引idx1中,c1是一个聚集索引cidx。问题就在这里!我们在p2中更新了c2这个值,所以sqlserver会自动更新包含c2列的非聚集索引:idx1。而idx1在哪里?就在我们刚才的select语句中。而对这个索引列的更改,意味着索引集合的某个行或者某些行,需要重新排列,而重新排列,需要一个X锁。

问题就这样被发现了,就是说,某个query使用非聚集索引来select数据,那么它会在非聚集索引上持有一个S锁。当有一些select的列不在该索引上,它需要根据rowid找到对应的聚集索引的那行,然后找到其他数据。而此时,第二个的查询中,update正在聚集索引上忙乎:定位、加锁、修改等。但因为正在修改的某个列,是另外一个非聚集索引的某个列,所以此时,它需要同时更改那个非聚集索引的信息,这就需要在那个非聚集索引上,加第二个X锁。select开始等待update的X锁,update开始等待select的S锁,死锁,就这样发生鸟。

添加了针对select和update同一个表的非聚集索引解决问题。那么,为什么我们增加了一个非聚集索引,死锁就消失鸟?我们看一下,按照上文中自动增加的索引之后的执行计划:

 

  1. SELECT c2, c3 FROM t1 WHERE c2 BETWEEN @p1 AND @p1+1
  2. |–Index Seek(OBJECT:([deadlocktest].[dbo].[t1].[_dta_index_t1_7_2073058421__K2_K1_3]), SEEK:([deadlocktest].[dbo].[t1].[c2] >= [@p1] AND [deadlocktest].[dbo].[t1].[c2] <= [@p1]+(1)) ORDERED FORWARD)

 

哦,对于clustered index的需求没有了,因为增加的覆盖索引已经足够把所有的信息都select出来。就这么简单。
实际上,在sqlserver 2005中,如果用profiler来抓eventid:1222,那么会出现一个死锁的图,很直观的说。

下面的方法,有助于将死锁减至最少(详细情况,请看SQLServer联机帮助,搜索:将死锁减至最少即可)。

·  按同一顺序访问对象。

·  避免事务中的用户交互。

·  保持事务简短并处于一个批处理中。

·  使用较低的隔离级别。

·  使用基于行版本控制的隔离级别。

–    将 READ_COMMITTED_SNAPSHOT 数据库选项设置为 ON,使得已提交读事务使用行版本控制。

–    使用快照隔离。

·   使用绑定连接。

下面我们在测试的同时开启trace profiler跟踪死锁视图(locks:deadlock graph).(当然也可以开启跟踪标记,或者应用扩展事件(xevents)等捕捉死锁)

创建测试对象code

  1. create table testklup
  2. ( clskey int not null,
  3. nlskey int not null,
  4. cont1 int not null,
  5. cont2 char(3000)
  6. )
  7. create unique clustered index inx_cls on testklup(clskey)
  8. create unique nonclustered index inx_nlcs on testklup(nlskey) include(cont1)
  9. insert into testklup select 1,1,100,‘aaa’
  10. insert into testklup select 2,2,200,‘bbb’
  11. insert into testklup select 3,3,300,‘ccc’

开启会话1 模拟高频update操作

—-模拟高频update操作

  1. declare @i int
  2. set @i=100
  3. while 1=1
  4. begin
  5. update testklup set cont1=@i
  6. where clskey=1
  7. set @i=@i+1
  8. end

开启会话2 模拟高频select操作

—-模拟高频select操作

  1. declare @cont2 char(3000)
  2. while 1=1
  3. begin
  4. select @cont2=cont2 from testklup where nlskey=1
  5. end

此时开启会话2执行一小段时间时我们就可以看到类似错误信息:

而在我们开启的跟踪中捕捉到了死锁.

死锁分析:可以看出由于读进程(108)请求写进程(79)持有的X锁被阻塞的同时,写进程(79)又申请读进程(108)锁持有的S锁.读执行计划图,写执行计划图。

(由于在默认隔离级别下(读提交)读申请S锁只是瞬间过程,读完立即释放,不会等待事务完成),所以在并发,执行频率不高的情形下不易出现.但我们模拟的高频情况使得S锁获得频率非常高,此时就出现了仅仅两个会话,一个读,一个写就造成了死锁现象.

死锁原因:读操作中的键查找造成的额外锁(聚集索引)需求

解决方案:在了解了死锁产生的原因后,解决起来就比较简单了.

我们可以从以下几个方面入手.

a 消除额外的键查找锁需的锁

b 读操作时取消获取锁

a.1我们可以创建覆盖索引使select语句中的查询列包含在指定索引中

CREATE NONCLUSTERED INDEX [inx_nlskey_incont2] ON [dbo].[testklup] ([nlskey] ASC) INCLUDE ( [cont2])

a.2 根据查询需求,分步执行,通过聚集索引获取查询列,避免键查找.’

declare @cont2 char(3000) declare @clskey intwhile 1=1 begin  select @clskey=clskey from testklup where nlskey=1     select @cont2=cont2 from testklup where clskey=@clskey end

b 通过改变隔离级别,使用乐观并发模式,读操作时源行无需锁

  1. declare @cont2 char(3000)
  2. while 1=1
  3. begin
  4. select @cont2=cont2 from testklup with(nolock) where nlskey=1
  5. end

结束语.我们在解决问题时,最好弄清问题的本质原因,通过问题点寻找出适合自己的环境的解决方案再实施.

SQLSERVER2008R2 索引建立的几点建议_ju523756055的专栏-CSDN博客

mikel阅读(611)

来源: SQLSERVER2008R2 索引建立的几点建议_ju523756055的专栏-CSDN博客

1、不要把聚集索引浪费在主键上,除非你只按主键查询

虽然SQL SERVER默认是在主键上建立聚集索引的,但实际应用中,这样做比较浪费。通常,我们会在每个表中都建立一个ID列,以区分每条数据,并且这个ID列是自动增大的,步长一般为1。此时,如果我们将这个列设为主键,SQL SERVER会将此列默认为聚集索引。这样做有好处,就是可以让您的数据在数据库中按照ID进行物理排序,但这样做实用价值不大。

从我们前面谈到的聚集索引的定义我们可以看出,使用聚集索引的最大好处就是能够根据查询要求,迅速缩小查询范围,避免全表扫描。在实际应用中,因为ID号是自动生成的,我们并不知道每条记录的ID 号,所以我们很难在实践中用ID号来进行查询。这就使让ID号这个主键作为聚集索引成为一种资源浪费。聚集索引相对与非聚集索引的优势是很明显的,而每个表中只能有一个聚集索引的规则,这使得聚集索引变得更加宝贵,应该用在其他查询频率高的字段上。其次,让每个ID号都不同的字段作为聚集索引也不符合“大数目的不同值情况下不应建立聚合索引”规则;当然,这种情况只是针对用户经常修改记录内容,特别是索引项的时候会负作用,但对于查询速度并没有影响。

2、索引的建立要根据实际应用需求来进行

并非是在任何字段上简单地建立索引就能提高查询速度。聚集索引建立的规则大致是“既不能绝大多数都相同,又不能只有极少数相同”。举个例子,在公文表的收发日期字段上建立聚合索引是比较合适的。在政务系统中,我们每天都会收一些文件,这些文件的发文日期将会相同,在发文日期上建立聚合索引对性能的提升应该是相当大的。在群集索引下,数据物理上按顺序存于数据页上,重复值也排列在一起,因而在范围查找时,可以先找到这个范围的起末点,且只在这个范围内扫描数据页,避免了大范围扫描,提高了查询速度。

另一个相反的例子:比如在雇员表的“性别”列上只有“男”与“女”两个不同值,因此就完全没必要建立索引。

3、在聚集索引中加入所有需要提高查询速度的字段,形成复合索引

根据一些实验的结果,我们可以得出一些可供参考的结论:

仅用复合聚集索引的起始列作为查询条件和同时用到复合聚集索引的全部列的查询,速度是几乎一样的,甚至比后者还要快(在查询结果集数目一样的情况下);

仅用复合聚集索引的非起始列作为查询条件的话,

这个索引是不起任何作用的。

复合聚集索引的所有列都用上,而且因为查询条件严格,查询结果少的话,会形成“索引覆盖”,性能可以达到最优。

最重要的一点:无论是否经常使用复合聚合索引的其他列,其起始列一定要是使用最频繁的列。

4.根据实践得出的一些其他经验,特定情况下有效

用聚合索引比用不是聚合索引的主键速度快;

用聚合索引比用一般的主键作order by速度快,特别是在小数据量情况;

使用聚合索引内的时间段,搜索时间会按数据占整个数据表的百分比成比例减少,而无论聚合索引使用了多少个;

日期列不会因为有分秒的输入而减慢查询速度;

由于改变一个表的内容,将会引起索引的变化。频繁的insert,update,delete语句将导致系统花费较大的代价进行索引更新,引起整体性能的下降。一般来讲,在对查询性能的要求高于对数据维护性能要求时,应该尽量使用索引,否则,就要慎重考虑一下付出的代价。在某些极端情况下,可先删除索引,再对数据库表更新大量数据,最后再重建索引,新建立的索引总是比较好用。

SQLServer2008R2正确使用索引

T1表 10000000万条数据,(插入时间36分钟,count(*)查询19秒,空间占用670M左右)

 

1.真正充分的利用索引
比如like ‘张%’ 就是符合SARG(符合扫描参数)标准
而like ‘%张’ 就不符合该标准

通配符%在字符串首字符的使用会导致索引无法使用,虽然实际应用中很难避免这样用,但还是应该对这种现象有所了解,至少知道此种用法性能是很低下的。

 

**********************************************

2.“非”操作符不满足SARG形式,使得索引无法使用
不满足SARG形式的语句最典型的情况就是包括非操作符的语句,如:NOT、!=、<>、!<、!>、NOT EXISTS、NOT IN、NOT LIKE等。
如果使用not 或者 <>,最好转换成别的方法,比如例子如下:

T1表 10000000万条数据,构建如下:(插入时间36分钟,count(*)查询19秒,空间占用670M左右)

DECLARE @i INT
SET @i = 1
WHILE @i<1000000
BEGIN
INSERT INTO t1 VALUES (‘zhang’+CONVERT(char(50), @i),’3.2′,77);
SET @i + 1;
END

三种查询方式:

SELECT * FROM t1 WHERE id <>300000
SELECT * FROM t1 WHERE id NOT IN (300000)
SELECT * FROM t1 WHERE id >299999 AND id < 300001

在执行计划中可以明显看出,使用最后一种方式而不是前面两种方式进行查询。
网上是这么说的,但自己做的试验100W条数据,开销计划是一样的。

 

*********************************************

 

3. 函数运算不满足SARG形式,使得索引无法使用
例:下列SQL条件语句中的列都建有恰当的索引,但执行速度却非常慢:

select * from record where substring(card_no,1,4)=′5378′(13秒)
select * from record where amount/30< 1000(11秒)
select * from record where convert(char(10),date,112)=′19991201′(10秒)

分析:

where子句中对列的任何操作结果都是在SQL运行时逐列计算得到的,因此它不得不进行全表扫描,而没有使用该列上面的索引;如果这些结果在查询编译时就能得到,那么就可以被SQL优化器优化,使用索引,避免表搜索,因此将SQL重写成下面这样:

select * from record where card_no like ′5378%′(< 1秒)
select * from record where amount < 1000*30(< 1秒)
select * from record where date= ′1999/12/01′ (< 1秒)

你会发现SQL明显快很多

待测试…….

 

**********************************************

 

4.尽量不要对建立了索引的字段,作任何的直接处理

select * from employs where first_name + last_name =’beill cliton’;

无法使用索引,改为:

select * from employee where
first_name = substr(‘beill cliton’,1,instr(‘beill cliton’,’ ‘)-1)
and
last_name = substr(‘beill cliton’,instr(‘beill cliton’,’ ‘)+1)

则可以使用索引

***********************************************

5.不同类型的索引效能是不一样的,应尽可能先使用效能高的
比如:数字类型的索引查找效率高于字符串类型,定长字符串char,nchar的索引效率高于变长字符串varchar,nvarchar的索引。
应该将
where username=’张三’ and age>20
改进为
where age>20 and username=’张三’
注意:此处,SQL的查询分析优化功能可以做到自动重排条件顺序,但还是建议预先手工排列好。

**************************************************

6.某些情况下IN 的作用与OR 相当 ,且都不能充分利用索引
例:表stuff有200000行,id_no上有非群集索引,请看下面这个SQL:
select count(*) from stuff where id_no in(′0′,′1′) (23秒)
我们期望它会根据每个or子句分别查找,再将结果相加,这样可以利用id_no上的索引;但实际上,它却采用了”OR策略”,即先取出满足每个or子句的行,存入临时数据库的工作表中,再建立唯一索引以去掉重复行,最后从这个临时表中计算结果。因此,实际过程没有利用id_no 上索引,并且完成时间还要受tempdb数据库性能的影响。
实践证明,表的行数越多,工作表的性能就越差,当stuff有620000行时,执行时间会非常长!如果确定不同的条件不会产生大量重复值,还不如将or子句分开:

select count(*) from stuff where id_no=′0′
select count(*) from stuff where id_no=′1′

得到两个结果,再用union作一次加法合算。因为每句都使用了索引,执行时间会比较短,

select count(*) from stuff where id_no=′0′
union
select count(*) from stuff where id_no=′1′

从实践效果来看,使用union在通常情况下比用or的效率要高的多,而exist关键字和in关键字在用法上类似,性能上也类似,都会产生全表扫描,效率比较低下,根据未经验证的说法,exist可能比in要快些。

***************************************************

7.使用变通的方法提高查询效率

like关键字支持通配符匹配,但这种匹配特别耗时。例如:select * from customer where zipcode like “21_ _ _”,即使在zipcode字段上已建立了索引,在这种情况下也可能还是采用全表扫描方式。如果把语句改为:select * from customer where zipcode >“21000”,在执行查询时就会利用索引,大大提高速度。但这种变通是有限制的,不应引起业务意义上的损失,对于邮政编码而言,zipcode like “21_ _ _” 和 zipcode >“21000” 意义是完全一致的。

*********************************************************人各有志,但富贵在天,人生允许彷徨,但不允许蹉跎.

 

8.order by按聚集索引列排序效率最高
排序是较耗时的操作,应尽量简化或避免对大型表进行排序,如缩小排序的列的范围,只在有索引的列上排序等等。
我们来看:(gid是主键,fariqi是聚合索引列)
select top 10000 gid,fariqi,reader,title from tgongwen
用时:196 毫秒。 扫描计数 1,逻辑读 289 次,物理读 1 次,预读 1527 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by gid asc
用时:4720毫秒。 扫描计数 1,逻辑读 41956 次,物理读 0 次,预读 1287 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc
用时:4736毫秒。 扫描计数 1,逻辑读 55350 次,物理读 10 次,预读 775 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi asc
用时:173毫秒。 扫描计数 1,逻辑读 290 次,物理读 0 次,预读 0 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi desc
用时:156毫秒。 扫描计数 1,逻辑读 289 次,物理读 0 次,预读 0 次。

同时,按照某个字段进行排序的时候,无论是正序还是倒序,速度是基本相当的。

 

********************************************************

9.关于节省数据查询系统开销方面的措施
(1)使用TOP尽量减少取出的数据量
(2)字段提取要按照“需多少、提多少”的原则,避免“select *”
字段大小越大,数目越多,select所耗费的资源就越多,比如取int类型的字段就会比取char的快很多。我们每少提取一个字段,数据的提取速度就会有相应的提升。提升的幅度根据舍弃的字段的大小来判断
(3)count(*) 与 count(字段) 方法比较
用count(*)和用 count(主键)的速度是相当的,而count(*)却比其他任何除主键以外的字段汇总速度要快,而且字段越长,汇总速度就越慢。如果用 count(*), SQL SERVER会自动查找最小字段来汇总。当然,如果您直接写count(主键)将会来的更直接些
(4)有嵌套查询时,尽可能在内层过滤掉数据
如果一个列同时在主查询和where子句中出现,很可能当主查询中的列值改变之后,子查询必须重新查询一次。而且查询嵌套层次越多,效率越低,因此应当尽量避免子查询。如果子查询不可避免,那么要在子查询中过滤掉尽可能多的行
(5)多表关联查询时,需注意表顺序,并尽可能早的过滤掉数据
在使用Join进行多表关联查询时候,应该使用系统开销最小的方案。连接条件要充份考虑带有索引的表、行数多的表,并注意优化表顺序;说的简单一点,就是尽可能早的将之后要做关联的数据量降下来。

一般情况下,SQLServer 会对表的连接作出自动优化。例如:
select name,no from A
join B on A. id=B.id
join C on C.id=A.id
where name=’wang’
尽管A表在From中先列出,然后才是B,最后才是C。但sql server可能会首先使用c表。它的选择原则是相对于该查询限制为单行或少数几行,就可以减少在其他表中查找的总数据量。绝大多数情况下,sql server 会作出最优的选择,但如果你发觉某个复杂的联结查询速度比预计的要慢,就可以使用SET FORCEPLAN语句强制sql server按照表出现顺序使用表。如上例加上:SET FORCEPLAN ON…….SET FORCEPLAN OFF 表的执行顺序将会按照你所写的顺序执行。在查询分析器中查看2种执行效率,从而选择表的连接顺序。SET FORCEPLAN的缺点是只能在存储过程中使用

PS:数据库的查询优化技术

数据库系统是管理信息系统的核心,基于数据库的联机事务处理(OLTP)以及联机分析处理(OLAP)是银行、企业、政府等部门最为重要的计算机应用之一。从大多数系统的应用实例来看,查询操作在各种数据库操作中所占据的比重最大,而查询操作所基于的SELECT语句在SQL语句中又是代价最大的语句。举例来说,如果数据的量积累到一定的程度,比如一个银行的账户数据库表信息积累到上百万甚至上千万条记录,全表扫描一次往往需要数十分钟,甚至数小时。如果采用比全表扫描更好的查询策略,往往可以使查询时间降为几分钟,由此可见查询优化技术的重要性。
笔者在应用项目的实施中发现,许多程序员在利用一些前端数据库开发工具(如PowerBuilder、Delphi等)开发数据库应用程序时,只注重用户界面的华丽,并不重视查询语句的效率问题,导致所开发出来的应用系统效率低下,资源浪费严重。因此,如何设计高效合理的查询语句就显得非常重要。本文以应用实例为基础,结合数据库理论,介绍查询优化技术在现实系统中的运用。

分析问题

许多程序员认为查询优化是DBMS(数据库管理系统)的任务,与程序员所编写的SQL语句关系不大,这是错误的。一个好的查询计划往往可以使程序性能提高数十倍。查询计划是用户所提交的SQL语句的集合,查询规划是经过优化处理之后所产生的语句集合。DBMS处理查询计划的过程是这样的:在做完查询语句的词法、语法检查之后,将语句提交给DBMS的查询优化器,优化器做完代数优化和存取路径的优化之后,由预编译模块对语句进行处理并生成查询规划,然后在合适的时间提交给系统处理执行,最后将执行结果返回给用户。在实际的数据库产品(如Oracle、Sybase等)的高版本中都是采用基于代价的优化方法,这种优化能根据从系统字典表所得到的信息来估计不同的查询规划的代价,然后选择一个较优的规划。虽然现在的数据库产品在查询优化方面已经做得越来越好,但由用户提交的SQL语句是系统优化的基础,很难设想一个原本糟糕的查询计划经过系统的优化之后会变得高效,因此用户所写语句的优劣至关重要。系统所做查询优化我们暂不讨论,下面重点说明改善用户查询计划的解决方案。
解决问题
下面以关系数据库系统Informix为例,介绍改善用户查询计划的方法。

1.合理使用索引
索引是数据库中重要的数据结构,它的根本目的就是为了提高查询效率。现在大多数的数据库产品都采用IBM最先提出的ISAM索引结构。索引的使用要恰到好处,其使用原则如下:
●在经常进行连接,但是没有指定为外键的列上建立索引,而不经常连接的字段则由优化器自动生成索引。
●在频繁进行排序或分组(即进行group by或order by操作)的列上建立索引。
●在条件表达式中经常用到的不同值较多的列上建立检索,在不同值少的列上不要建立索引。比如在雇员表的“性别”列上只有“男”与“女”两个不同值,因此就无必要建立索引。如果建立索引不但不会提高查询效率,反而会严重降低更新速度。
●如果待排序的列有多个,可以在这些列上建立复合索引(compound index)。
●使用系统工具。如Informix数据库有一个tbcheck工具,可以在可疑的索引上进行检查。在一些数据库服务器上,索引可能失效或者因为频繁操作而使得读取效率降低,如果一个使用索引的查询不明不白地慢下来,可以试着用tbcheck工具检查索引的完整性,必要时进行修复。另外,当数据库表更新大量数据后,删除并重建索引可以提高查询速度。

2.避免或简化排序
应当简化或避免对大型表进行重复的排序。当能够利用索引自动以适当的次序产生输出时,优化器就避免了排序的步骤。以下是一些影响因素:
●索引中不包括一个或几个待排序的列;
●group by或order by子句中列的次序与索引的次序不一样;
●排序的列来自不同的表。
为了避免不必要的排序,就要正确地增建索引,合理地合并数据库表(尽管有时可能影响表的规范化,但相对于效率的提高是值得的)。如果排序不可避免,那么应当试图简化它,如缩小排序的列的范围等。

3.消除对大型表行数据的顺序存取
在嵌套查询中,对表的顺序存取对查询效率可能产生致命的影响。比如采用顺序存取策略,一个嵌套3层的查询,如果每层都查询1000行,那么这个查询就要查询10亿行数据。避免这种情况的主要方法就是对连接的列进行索引。例如,两个表:学生表(学号、姓名、年龄……)和选课表(学号、课程号、成绩)。如果两个表要做连接,就要在“学号”这个连接字段上建立索引。
还可以使用并集来避免顺序存取。尽管在所有的检查列上都有索引,但某些形式的where子句强迫优化器使用顺序存取。下面的查询将强迫对orders表执行顺序操作:
SELECT * FROM orders WHERE (customer_num=104 AND order_num>1001) OR order_num=1008
虽然在customer_num和order_num上建有索引,但是在上面的语句中优化器还是使用顺序存取路径扫描整个表。因为这个语句要检索的是分离的行的集合,所以应该改为如下语句:
SELECT * FROM orders WHERE customer_num=104 AND order_num>1001
UNION
SELECT * FROM orders WHERE order_num=1008
这样就能利用索引路径处理查询。

4.避免相关子查询
一个列的标签同时在主查询和where子句中的查询中出现,那么很可能当主查询中的列值改变之后,子查询必须重新查询一次。查询嵌套层次越多,效率越低,因此应当尽量避免子查询。如果子查询不可避免,那么要在子查询中过滤掉尽可能多的行。

5.避免困难的正规表达式
MATCHES和LIKE关键字支持通配符匹配,技术上叫正规表达式。但这种匹配特别耗费时间。例如:SELECT * FROM customer WHERE zipcode LIKE “98_ _ _”
即使在zipcode字段上建立了索引,在这种情况下也还是采用顺序扫描的方式。如果把语句改为SELECT * FROM customer WHERE zipcode >“98000”,在执行查询时就会利用索引来查询,显然会大大提高速度。
另外,还要避免非开始的子串。例如语句:SELECT * FROM customer WHERE zipcode[2,3] >“80”,在where子句中采用了非开始子串,因而这个语句也不会使用索引。
6.使用临时表加速查询
把表的一个子集进行排序并创建临时表,有时能加速查询。它有助于避免多重排序操作,而且在其他方面还能简化优化器的工作。例如:
SELECT cust.name,rcvbles.balance,……other columns
FROM cust,rcvbles
WHERE cust.customer_id = rcvlbes.customer_id
AND rcvblls.balance>0
AND cust.postcode>“98000”
ORDER BY cust.name
如果这个查询要被执行多次而不止一次,可以把所有未付款的客户找出来放在一个临时文件中,并按客户的名字进行排序:
SELECT cust.name,rcvbles.balance,……other columns
FROM cust,rcvbles
WHERE cust.customer_id = rcvlbes.customer_id
AND rcvblls.balance>0
ORDER BY cust.name
INTO TEMP cust_with_balance
然后以下面的方式在临时表中查询:
SELECT * FROM cust_with_balance
WHERE postcode>“98000”
临时表中的行要比主表中的行少,而且物理顺序就是所要求的顺序,减少了磁盘I/O,所以查询工作量可以得到大幅减少。
注意:临时表创建后不会反映主表的修改。在主表中数据频繁修改的情况下,注意不要丢失数据。

7.用排序来取代非顺序存取
非顺序磁盘存取是最慢的操作,表现在磁盘存取臂的来回移动。SQL语句隐藏了这一情况,使得我们在写应用程序时很容易写出要求存取大量非顺序页的查询。
有些时候,用数据库的排序能力来替代非顺序的存取能改进查询。

SQLSERVER2008R2正确使用索引 - 李华丽 - 博客园

mikel阅读(702)

来源: SQLSERVER2008R2正确使用索引 – 李华丽 – 博客园

T1表 10000000万条数据,(插入时间36分钟,count(*)查询19秒,空间占用670M左右)

1.真正充分的利用索引
比如like ‘张%’ 就是符合SARG(符合扫描参数)标准
而like ‘%张’ 就不符合该标准

通配符%在字符串首字符的使用会导致索引无法使用,虽然实际应用中很难避免这样用,但还是应该对这种现象有所了解,至少知道此种用法性能是很低下的。

**********************************************

2.“非”操作符不满足SARG形式,使得索引无法使用
不满足SARG形式的语句最典型的情况就是包括非操作符的语句,如:NOT、!=、<>、!<、!>、NOT EXISTS、NOT IN、NOT LIKE等。
如果使用not 或者 <>,最好转换成别的方法,比如例子如下:

T1表 10000000万条数据,构建如下:(插入时间36分钟,count(*)查询19秒,空间占用670M左右)

DECLARE @i INT
SET @i = 1
WHILE @i<1000000
BEGIN
INSERT INTO t1 VALUES (‘zhang’+CONVERT(char(50), @i),’3.2’,77);
SET @i + 1;
END

三种查询方式:

SELECT * FROM t1 WHERE id <>300000
SELECT * FROM t1 WHERE id NOT IN (300000)
SELECT * FROM t1 WHERE id >299999 AND id < 300001

在执行计划中可以明显看出,使用最后一种方式而不是前面两种方式进行查询。
网上是这么说的,但自己做的试验100W条数据,开销计划是一样的。

*********************************************

3. 函数运算不满足SARG形式,使得索引无法使用
例:下列SQL条件语句中的列都建有恰当的索引,但执行速度却非常慢:

select * from record where substring(card_no,1,4)=′5378′(13秒)
select * from record where amount/30< 1000(11秒)
select * from record where convert(char(10),date,112)=′19991201′(10秒)

分析:

where子句中对列的任何操作结果都是在SQL运行时逐列计算得到的,因此它不得不进行全表扫描,而没有使用该列上面的索引;如果这些结果在查询编译时就能得到,那么就可以被SQL优化器优化,使用索引,避免表搜索,因此将SQL重写成下面这样:

select * from record where card_no like ′5378%′(< 1秒)
select * from record where amount < 1000*30(< 1秒)
select * from record where date= ′1999/12/01′ (< 1秒)

你会发现SQL明显快很多

待测试…….

**********************************************

4.尽量不要对建立了索引的字段,作任何的直接处理

select * from employs where first_name + last_name =’beill cliton’;

无法使用索引,改为:

select * from employee where
first_name = substr(‘beill cliton’,1,instr(‘beill cliton’,’ ‘)-1)
and
last_name = substr(‘beill cliton’,instr(‘beill cliton’,’ ‘)+1)

则可以使用索引

***********************************************

5.不同类型的索引效能是不一样的,应尽可能先使用效能高的
比如:数字类型的索引查找效率高于字符串类型,定长字符串char,nchar的索引效率高于变长字符串varchar,nvarchar的索引。
应该将
where username=’张三’ and age>20
改进为
where age>20 and username=’张三’
注意:此处,SQL的查询分析优化功能可以做到自动重排条件顺序,但还是建议预先手工排列好。

**************************************************

6.某些情况下IN 的作用与OR 相当 ,且都不能充分利用索引
例:表stuff有200000行,id_no上有非群集索引,请看下面这个SQL:
select count(*) from stuff where id_no in(′0′,′1′) (23秒)
我 们期望它会根据每个or子句分别查找,再将结果相加,这样可以利用id_no上的索引;但实际上,它却采用了”OR策略”,即先取出满足每个or子句的 行,存入临时数据库的工作表中,再建立唯一索引以去掉重复行,最后从这个临时表中计算结果。因此,实际过程没有利用id_no 上索引,并且完成时间还要 受tempdb数据库性能的影响。
实践证明,表的行数越多,工作表的性能就越差,当stuff有620000行时,执行时间会非常长!如果确定不同的条件不会产生大量重复值,还不如将or子句分开:

select count(*) from stuff where id_no=′0′
select count(*) from stuff where id_no=′1′

得到两个结果,再用union作一次加法合算。因为每句都使用了索引,执行时间会比较短,

select count(*) from stuff where id_no=′0′
union
select count(*) from stuff where id_no=′1′

从实践效果来看,使用union在通常情况下比用or的效率要高的多,而exist关键字和in关键字在用法上类似,性能上也类似,都会产生全表扫描,效率比较低下,根据未经验证的说法,exist可能比in要快些。

***************************************************

7.使用变通的方法提高查询效率

like关键字支持通配符匹配,但这种匹配特别耗时。例 如:select * from customer where zipcode like “21_ _ _”,即使在zipcode字段上已建立了索 引,在这种情况下也可能还是采用全表扫描方式。如果把语句改 为:select * from customer where zipcode >“21000”,在执行查询时就会利用索引,大大提高速度。但 这种变通是有限制的,不应引起业务意义上的损失,对于邮政编码而 言,zipcode like “21_ _ _” 和 zipcode >“21000” 意义是完全一致的。

*********************************************************人各有志,但富贵在天,人生允许彷徨,但不允许蹉跎.

8.order by按聚集索引列排序效率最高
排序是较耗时的操作,应尽量简化或避免对大型表进行排序,如缩小排序的列的范围,只在有索引的列上排序等等。
我们来看:(gid是主键,fariqi是聚合索引列)
select top 10000 gid,fariqi,reader,title from tgongwen
用时:196 毫秒。 扫描计数 1,逻辑读 289 次,物理读 1 次,预读 1527 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by gid asc
用时:4720毫秒。 扫描计数 1,逻辑读 41956 次,物理读 0 次,预读 1287 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by gid desc
用时:4736毫秒。 扫描计数 1,逻辑读 55350 次,物理读 10 次,预读 775 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi asc
用时:173毫秒。 扫描计数 1,逻辑读 290 次,物理读 0 次,预读 0 次。

select top 10000 gid,fariqi,reader,title from tgongwen order by fariqi desc
用时:156毫秒。 扫描计数 1,逻辑读 289 次,物理读 0 次,预读 0 次。

同时,按照某个字段进行排序的时候,无论是正序还是倒序,速度是基本相当的。

********************************************************

9.关于节省数据查询系统开销方面的措施
(1)使用TOP尽量减少取出的数据量
(2)字段提取要按照“需多少、提多少”的原则,避免“select *”
字段大小越大,数目越多,select所耗费的资源就越多,比如取int类型的字段就会比取char的快很多。我们每少提取一个字段,数据的提取速度就会有相应的提升。提升的幅度根据舍弃的字段的大小来判断
(3)count(*) 与 count(字段) 方法比较
用 count(*)和用 count(主键)的速度是相当的,而count(*)却比其他任何除主键以外的字段汇总速度要快,而且字段越长,汇总速度就越 慢。如果用 count(*), SQL SERVER会自动查找最小字段来汇总。当然,如果您直接写count(主键)将会来的更直接些
(4)有嵌套查询时,尽可能在内层过滤掉数据
如果一个列同时在主查询和where子句中出现,很可能当主查询中的列值改变之后,子查询必须重新查询一次。而且查询嵌套层次越多,效率越低,因此应当尽量避免子查询。如果子查询不可避免,那么要在子查询中过滤掉尽可能多的行
(5)多表关联查询时,需注意表顺序,并尽可能早的过滤掉数据
在使用Join进行多表关联查询时候,应该使用系统开销最小的方案。连接条件要充份考虑带有索引的表、行数多的表,并注意优化表顺序;说的简单一点,就是尽可能早的将之后要做关联的数据量降下来。

一般情况下,SQLServer 会对表的连接作出自动优化。例如:
select name,no from A
join B on A. id=B.id
join C on C.id=A.id
where name=’wang’
尽 管A表在From中先列出,然后才是B,最后才是C。但sql server可能会首先使用c表。它的选择原则是相对于该查询限制为单行或少数几行,就可 以减少在其他表中查找的总数据量。绝大多数情况下,sql server 会作出最优的选择,但如果你发觉某个复杂的联结查询速度比预计的要慢,就可以使 用SET FORCEPLAN语句强制sql server按照表出现顺序使用表。如上例加 上:SET FORCEPLAN ON…….SET FORCEPLAN OFF 表的执行顺序将会按照你所写的顺序执行。在查询分析器中查看2种执行效 率,从而选择表的连接顺序。SET FORCEPLAN的缺点是只能在存储过程中使用

SQL Server死锁问题:事务(进程 ID x)与另一个进程被死锁在 锁 | 通信缓冲区资源上并且已被选作死锁牺牲品。请重新运行该事务。 - A汉克先生 - 博客园

mikel阅读(4143)

来源: SQL Server死锁问题:事务(进程 ID x)与另一个进程被死锁在 锁 | 通信缓冲区资源上并且已被选作死锁牺牲品。请重新运行该事务。 – A汉克先生 – 博客园

### The error occurred while setting parameters
### SQL: update ERP_SCjh_zzc_pl set IF_TONGBU=1 where (IF_TONGBU=0 or IF_TONGBU is null) and djno=? and djlb=?
### Cause: com.microsoft.SQLServer.jdbc.SQLServerException: 事务(进程 ID 191)与另一个进程被死锁在 锁 | 通信缓冲区 资源上,并且已被选作死锁牺牲品。请重新运行该事务。
; SQL []; 事务(进程 ID 191)与另一个进程被死锁在 锁 | 通信缓冲区 资源上,并且已被选作死锁牺牲品。请重新运行该事务。; nested exception is com.microsoft.SQLServer.jdbc.SQLServerException: 事务(进程 ID 191)与另一个进程被死锁在 锁 | 通信缓冲区 资源上,并且已被选作死锁牺牲品。请重新运行该事务。
[2019-08-24 17:26:52,077] [qtp1891502635-26] [ERROR] [ExceptionController.java:26] ==>Exception Msg:

 

 

感觉这篇文章写的不错,读了三遍不是太明白,记录下来:
https://www.cnblogs.com/happyhippy/archive/2008/11/14/1333922.html

解决办法:

    1. SQLServer自动选择一条SQL作死锁的牺牲品;
    2. 按同一顺序访问对象;
    3. SELECT语句加with (onlock)提示;
    4. 使用较低的隔离级别
    5. 在SQL前加SET LOCK_TIMEOUT timeout_period,当请求锁超过设定的timeout_period时间后,就会终止当前SQL的执行
    6. 使用基于行版本控制的隔离级别
    7. 使用绑定连接

锁相关:https://www.cnblogs.com/wynn0123/p/4633776.html