• 详细解说NParsing框架实现原理 —— 2)参数组件(ObParameter)


    通常的三层架构的代码中,为了方便,大家往往会把WHERE条件的字符串放到业务逻辑层拼接。这样会带来以下几个问题:
    1、没有参数化,只用字符拼接的WHERE条件,非常不安全,容易被SQL注入。
    2、在写程序时,随意性太大,程序不够严谨。比如一开始是查询一个表,条件中没加表名。后来由于业务需要,要对多个表关连,改了数据持久层,结果运行肯定出错。
    3、在多表关连查询时,条件又非常复杂时,拼接的WHERE条件字符串巨长。看得头晕眼花。
    4、程序移植性太差。不同数据库之间,虽然大部分SQL语法是相同的,但也还有小部分的东西不一样,你把WHERE条件写死在业务层了,哪天要换个数据库,还要重写一套业务逻辑不成?

    NParsing框架中的参数组件(ObParameter)就是为了解决这些问题,以定义简单、标准的操作接口,实现不同数据库WHERE条件生成。
    使用参数化、保证程序员书写严谨、书写格式简单易懂、关键是移植性强。

    参数组件,实际上也就是一个封装了拼接SQL语句中WHERE条件方法组件。提供统一的调用方法。

    接口如下:

    代码
     1 using System.Collections.Generic;
     2 using System.Data.Common;
     3 
     4 namespace DotNet.Frameworks.NParsing.Interface
     5 {
     6     public interface IObParameter
     7     {
     8         /// <summary>
     9         /// 值 null, DbTerm, DbNTerm
    10         /// </summary>
    11         object Value { getset; }
    12 
    13         /// <summary>
    14         /// 平级兄弟列表
    15         /// </summary>
    16         IList<IObParameter> Brothers { getset; }
    17 
    18         /// <summary>
    19         /// 0 无兄弟 1 AND 2 OR
    20         /// </summary>
    21         int BrotherType { getset; }
    22 
    23         /// <summary>
    24         /// SQL条件语句
    25         /// </summary>
    26         string ToString(ref IList<DbParameter> dbParameters);
    27 
    28         /// <summary>
    29         /// 平级AND条件
    30         /// </summary>
    31         /// <param name="iObParameter"></param>
    32         /// <returns></returns>
    33         IObParameter And(IObParameter iObParameter);
    34 
    35         /// <summary>
    36         /// 平级OR条件
    37         /// </summary>
    38         /// <param name="iObParameter"></param>
    39         /// <returns></returns>
    40         IObParameter Or(IObParameter iObParameter);
    41     }
    42 }

    在参数组件实现中,需要解决以下问题:
    1、解决特殊条件,比如IS NULL,NOT IS NULL。
    2、解决条件中括号的运用。
    3、解决书写时程序的严谨性。
    4、解决生成参数化的WHERE条件。
    5、解决WHERE条件延迟生成。

    一、如何解决特殊条件(IS NULL或NOT IS NULL)的处理。

    普通单条件的组成,有“表名”、“字段名”、“条件符号”、“值”四个部分。
    如 Table1.column1=@value1
    特殊单条件的组成,有“表名”、“字段名”、“IS NULL或NOT IS NULL”三个部分。
    如 Table1.column1 IS NULL

    这里有两个东西是可以固定的,由用户选的。“条件符号”和“IS NULL或NOT IS NULL”。所以我定义了两个枚举。
    条件符号(DbSymbol)代码如下:

    代码
     1 namespace DotNet.Frameworks.NParsing.Common
     2 {
     3     public enum DbSymbol
     4     {
     5         Equal,      //=
     6         NotEqual,   //<>
     7         LessEqual,  //<=
     8         ThanEqual,  //>=
     9         Less,       //>
    10         Than,       //<
    11         Like, 
    12         LikeLeft,
    13         LikeRight,
    14         In,
    15         NotIn
    16     }
    17 }

    特殊条件值(DbValue)代码如下:

    1 namespace DotNet.Frameworks.NParsing.Common
    2 {
    3     public enum DbValue
    4     {
    5         IsNull,
    6         NotIsNull
    7     }
    8 }

    创建普通条件代码:

    代码
     1 /// <summary>
     2 /// 创建数据库操作接口,指定connectionStringName数据库连接配置结点名称
     3 /// </summary>
     4 /// <typeparam name="M">对象模型</typeparam>
     5 /// <param name="connectionStringName">数据库连接配置结点名称</param>
     6 /// <returns></returns>
     7 public static IObHelper<M> Create<M>(string connectionStringName)
     8 {
     9     var connectionStringSettings = ConfigurationManager.ConnectionStrings[connectionStringName];
    10     #if (DEBUG)
    11     if (connectionStringSettings == null)
    12     {
    13         throw new Exception(string.Format("数据库连接配置节点{0}未找到", connectionStringName));
    14     }
    15     #endif
    16     Type t = typeof (M);
    17     var className = CLASS_NAME + "[[" + t.FullName + "," + t.Assembly.FullName + "]]";
    18     t = Assembly.Load(ASSEMBLY_STRING).GetType(className);
    19     var parameters = new[]
    20                     {
    21                         connectionStringSettings.ConnectionString,
    22                         connectionStringSettings.ProviderName
    23                     };
    24     return (IObHelper<M>) Activator.CreateInstance(t, parameters);
    25 }

    创建特殊条件代码:

    代码
     1 /// <summary>
     2 /// 创建数据库操作接口,传入数据库连接字符串和数据库操作类库名称,方便数据库连接字符串加密存储
     3 /// </summary>
     4 /// <typeparam name="M">对象模型</typeparam>
     5 /// <param name="connectionString">数据库连接字符串</param>
     6 /// <param name="providerName">数据库操作类库名称</param>
     7 /// <returns></returns>
     8 public static IObHelper<M> Create<M>(string connectionString, string providerName)
     9 {
    10     Type t = typeof(M);
    11     var className = CLASS_NAME + "[[" + t.FullName + "," + t.Assembly.FullName + "]]";
    12     t = Assembly.Load(ASSEMBLY_STRING).GetType(className);
    13     var parameters = new[]
    14                      {
    15                          connectionString,
    16                          providerName
    17                      };
    18     return (IObHelper<M>)Activator.CreateInstance(t, parameters);
    19 }

    二、如何解决条件中括号的运用?

    整个WHERE条件字符串中,因为优先级的关系,必须要用到括号。
    在And、Or方法中我是这样实现的,每个IObParameter实例都有一个平级条件列表(Brothers)。在当前参数实例的Brothers列表中的参数实例,都中本参数实例是平级的(这句话有绕,大家理解理解吧)。如果Brothers例表中有一个以上参数实例时,就会把本参数实例和Brothers列表中的参数实例用括号括起来。具体实现如下代码(SQLServer):

    代码
     1 /// <summary>
     2 /// 创建Where条件语句
     3 /// </summary>
     4 /// <param name="iObParameter">参数</param>
     5 /// <param name="dbParameter">回带数据库参数</param>
     6 /// <returns></returns>
     7 private static string CreateWhere(IObParameter iObParameter, ref IList<DbParameter> dbParameter)
     8 {
     9     string sqlWhere = string.Empty;
    10     if(iObParameter.Value  is DbTerm)
    11     {
    12         sqlWhere = CreateSymbolWhere(iObParameter, ref dbParameter);
    13     }
    14     else if(iObParameter.Value is DbNTerm)
    15     {
    16         sqlWhere = CreateValueWhere(iObParameter);
    17     }
    18     int iBrotherCount = iObParameter.Brothers.Count;
    19     for (int i = 0; i < iBrotherCount; i++)
    20     {
    21         var brother = iObParameter.Brothers[i];
    22         switch (brother.BrotherType)
    23         {
    24             case 1:
    25                 sqlWhere += " AND ";
    26                 break;
    27             case 2:
    28                 sqlWhere += " OR ";
    29                 break;
    30         }
    31         string andorWhere = "{0}";
    32         if (iObParameter.Brothers[i].Brothers.Count > 0)
    33         {
    34             andorWhere = "(" + andorWhere + ")";
    35         }
    36         sqlWhere += string.Format(andorWhere, CreateWhere(brother, ref dbParameter));
    37     }
    38     return sqlWhere;
    39 }

    整个条件使用括号,在构造中实现。代码如下:

    代码
     1 private const string ASSEMBLY_STRING = "DotNet.Frameworks.NParsing.DbUtilities";
     2 private const string CLASS_NAME = ASSEMBLY_STRING + ".ObParameter";
     3 /// <summary>
     4 /// 创建子条件
     5 /// </summary>
     6 /// <param name="iObParameter">参数</param>
     7 /// <returns></returns>
     8 public static IObParameter Create(IObParameter iObParameter)
     9 {
    10     Type t = Assembly.Load(ASSEMBLY_STRING).GetType(CLASS_NAME);
    11     return (IObParameter)Activator.CreateInstance(t, iObParameter);
    12 }

    三、如何解决书写时程序的严谨性?
    上面说过,
    普通单条件的组成,有“表名”、“字段名”、“条件符号”、“值”四个部分。
    如 Table1.column1=@value1
    特殊单条件的组成,有“表名”、“字段名”、“条件符号”、“值”、“IS NULL或NOT IS NULL”三个部分。
    如 Table1.column1 IS NULL

    “表名”、“字段名”、“条件符号”、“IS NULL或NOT IS NULL”都是要外部传入的。怎么保证用户书写准确。我是这么做的:
    public static IObParameter Create<M>(string propertyName, DbValue dbValue)
    public static IObParameter Create<M>(string propertyName, DbSymbol dbSymbol, object value)
    表名用对象模型以范型方式传入,
    字段名以字符串参数方式传入,
    “条件符号”、特殊条件值“IS NULL或NOT IS NULL”,用枚举定义,
    在创建时验正M对象模型中有没有propertyName属性,以保正条件准确性。

    四、如何解决生成参数化的WHERE条件?
    参数化要注意的是,防止参数重名。如在UPDATE语句中,可能这个字段既是数据更新字段,也是条件字段。


    以SQL Server代码为例:

    代码
     1 /// <summary>
     2 /// 创建带符号的Where条件语句
     3 /// </summary>
     4 /// <param name="iObParameter">参数</param>
     5 /// <param name="dbParameter">回带数据库参数</param>
     6 /// <returns></returns>
     7 private static string CreateSymbolWhere(IObParameter iObParameter, ref IList<DbParameter> dbParameter)
     8 {
     9     string sqlWhere = string.Empty;
    10     var dbTerm = (DbTerm)iObParameter.Value;
    11     string parameterName = "@" + dbTerm.ColumnName;
    12 
    13     #region 防止重复参数名
    14 
    15     int i = 0;
    16     foreach (var parameter in dbParameter)
    17     {
    18         var pn = parameter.ParameterName;
    19         if (pn.Length > parameterName.Length && pn.Substring(0, parameterName.Length).Equals(parameterName))
    20             i++;
    21         else if (pn.Length == parameterName.Length && pn.Equals(parameterName))
    22             i++;
    23     }
    24     parameterName += i == 0 ? "" : i.ToString();
    25 
    26     #endregion
    27 
    28     switch (dbTerm.DbSymbol)
    29     {
    30         case DbSymbol.Equal:
    31             sqlWhere += string.Format("{0}.{1} = {2}", dbTerm.TableName, dbTerm.ColumnName, parameterName);
    32             break;
    33         case DbSymbol.NotEqual:
    34             sqlWhere += string.Format("{0}.{1} <> {2}", dbTerm.TableName, dbTerm.ColumnName, parameterName);
    35             break;
    36         case DbSymbol.LessEqual:
    37             sqlWhere += string.Format("{0}.{1} <= {2}", dbTerm.TableName, dbTerm.ColumnName, parameterName);
    38             break;
    39         case DbSymbol.ThanEqual:
    40             sqlWhere += string.Format("{0}.{1} >= {2}", dbTerm.TableName, dbTerm.ColumnName, parameterName);
    41             break;
    42         case DbSymbol.Less:
    43             sqlWhere += string.Format("{0}.{1} < {2}", dbTerm.TableName, dbTerm.ColumnName, parameterName);
    44             break;
    45         case DbSymbol.Than:
    46             sqlWhere += string.Format("{0}.{1} > {2}", dbTerm.TableName, dbTerm.ColumnName, parameterName);
    47             break;
    48         case DbSymbol.Like:
    49         case DbSymbol.LikeLeft:
    50         case DbSymbol.LikeRight:
    51             sqlWhere += string.Format("{0}.{1} LIKE {2}", dbTerm.TableName, dbTerm.ColumnName, parameterName);
    52             break;
    53         case DbSymbol.In:
    54             sqlWhere += string.Format("{0}.{1} IN ({2})", dbTerm.TableName, dbTerm.ColumnName, parameterName);
    55             break;
    56         case DbSymbol.NotIn:
    57             sqlWhere += string.Format("{0}.{1} NOT IN ({2})", dbTerm.TableName, dbTerm.ColumnName, parameterName);
    58             break;
    59     }
    60     switch (dbTerm.DbSymbol)
    61     {
    62         case DbSymbol.Like:
    63             dbParameter.Add(new SqlParameter(parameterName, "%" + dbTerm.Value + "%"));
    64             break;
    65         case DbSymbol.LikeLeft:
    66             dbParameter.Add(new SqlParameter(parameterName, "%" + dbTerm.Value));
    67             break;
    68         case DbSymbol.LikeRight:
    69             dbParameter.Add(new SqlParameter(parameterName, dbTerm.Value + "%"));
    70             break;
    71         default:
    72             dbParameter.Add(new SqlParameter(parameterName, dbTerm.Value));
    73             break;
    74     }
    75     return sqlWhere;
    76 }

    五、什么是WHERE条件延迟生成?
    就是不在创建ObParameter时生成WHERE字符串,而是在控制器组件调用具体操作方法时生成。好处是,不用在创建ObParameter时,告诉它我是要生成什么类型数据库的WHERE条件。

    注:NParsing框架及Demo程序,将会在“引言(NParsing框架功能简介、NParsing的由来) ”中提供下载。

    详细解说NParsing框架实现原理 —— 1)控制器组件(ObHelper)

    引言(NParsing框架功能简介、NParsing的由来)

  • 相关阅读:
    辅助性的“比较操作符”
    辅助性的“比较操作符”
    浙江一乘客没赶上火车退票不成把票撕了 结果"悲剧"了
    美国超震撼短片-梦想
    在HTML文件中加入空格
    揭秘人造肉
    不能发布网站简讯
    KMPlayer
    文件四处盖章签字等
    冬天到了如何御寒
  • 原文地址:https://www.cnblogs.com/zhidian/p/1725356.html
Copyright © 2020-2023  润新知