详细解说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的由来)

posted @ 2010-04-30 22:12  支点  阅读(1454)  评论(10编辑  收藏  举报