自己动手写ORM(02):Sql生成器实现

Posted i++ˇ

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了自己动手写ORM(02):Sql生成器实现相关的知识,希望对你有一定的参考价值。

回顾:

  上一节中鄙人通过解析表达式树生成Sql碎片,其中我也把解析表达式类代码贴了出来,文章发布之后我对ExpressionAnalyzer类做了些改动,下面我还会将代码贴出来,废话不多说,直接进入今天的主题。

实体类设计:

  首先,我觉得要想直接通过实体类生成Sql语句,那么你可能要知道这个实体类对应数据库表中的主键和外键是什么,在此我加入了两个特性来标识主键和外键关系。如下

/// <summary>
    /// 外键表特性(导航属性)
    /// </summary>
    public class GuidanceAttribute : System.Attribute
    {
        /// <summary>
        /// 依赖字段
        /// </summary>
        public string DepandField { get; set; }
    }

 public class MessageAttribute : System.Attribute
    {
        /// <summary>
        /// 链接字符串名
        /// </summary>
        public string ConStr { get; set; }
    }
 public class PrimaryKeyAttribute : System.Attribute
    {

    }

  实体类如下:

 1 using LC.Model;
 2 using LC.Model.Attribute;
 3 using System;
 4 using System.Collections.Generic;
 5 using System.Linq;
 6 using System.Text;
 7 using System.Threading.Tasks;
 8 using LC.Model.Common;
 9 using LC.Test;
10 using LC.Test.Model;
11 
12 namespace LC.Model
13 {
14     [Message(ConStr = "sql")]
15     public class SYS_User
16     {
17         [PrimaryKey]
18         public string Id { get; set; }
19         public string UserName { get; set; }
20 
21         public Guid? RoleId { get; set; }
22         public Guid? CityId { get; set; }
23         [Guidance(DepandField = "RoleId")]
24         public SYS_Role Role { get; set; }
25 
26         [Guidance(DepandField = "CityId")]
27         public City City { get; set; }
28 
29         public int Gender { get; set; }
30 
31         public bool Deleted { get; set; }
32 
33         public DateTime CreateTime { get; set; }
34     }
35 }
View Code

  那么在生成sql的过程中,我需要知道实体所对应表的主键和外键关系,此关系我保存在类TableInfo中:

1  public class TableInfo
2     {
3         public string ConStr { get; set; }
4         public string RName { get; set; }
5         public string TableName { get; set; }
6         public string PrimaryKey { get; set; }
7         public Dictionary<string, TableInfo> ForeignRefs { get; set; }
8     }
View Code

  具体怎么将实体类转换成TableInfo,在此我暂时使用反射,不过多解释,因为暂时还不知道有什么方法不用反射获取特性。

  有了TableInfo,我们就可以轻松的生成Sql了。

查询命令生成器设计:

  下面上图,这是我用画图工具话的,不要问我为什么,因为电脑没有建模工具。。。

  

  直接上代码:

  

 1     public interface ICreator
 2     {
 3         SqlDebris Debris { get; set; }
 4     }
 5 
 6     public class BaseCreator : ICreator
 7     {
 8         public SqlDebris Debris { get; set; }
 9         public BaseCreator()
10         {
11             Debris = new SqlDebris();
12         }
13     }
14     public interface IQuery : ICreator
15     {
16         void CreateFrom(AnalysisTable data, TableInfo tableInfo);
17         void CreateSelect(AnalysisData data);
18         void CreateWhere(AnalysisData data);
19         void AppendOrder(AnalysisData data, OrderTypeEnum orderType);
20         void AppendSkip(int count);
21         void AppendTake(int count);
22         void GetCount();
23     }
View Code

  下面是QueryCreator的实现:

  1  public class QueryCreator : BaseCreator, IQuery
  2     {
  3         public virtual void CreateSelect(AnalysisData data)
  4         {
  5             var primaryTable = data.Table;
  6             StringBuilder sb = new StringBuilder();
  7             //默认查询全部列
  8             if (data.StackList.Count <= 0)
  9             {
 10                 sb.AppendFormat("[{0}].*", primaryTable.RName);
 11             }
 12             else
 13             {
 14                 //查询部分列
 15                 for (int i = 0; i < data.StackList.Count; i += 3)
 16                 {
 17                     sb.AppendFormat("{0} {1} {2},", data.StackList[i], data.StackList[i + 1], data.StackList[i + 2]);
 18                 }
 19                 sb.Remove(sb.Length - 1, 1);
 20             }
 21             Debris.Select = sb.ToString();
 22         }
 23         public virtual void CreateFrom(AnalysisTable anlyTable, TableInfo tableInfo)
 24         {
 25             if (null == anlyTable)
 26             {
 27                 throw new BusinessException(BusinessRes.TableCanNotBeEmpty);
 28             }
 29             //默认排序信息
 30             if (string.IsNullOrEmpty(Debris.Order))
 31             {
 32                 Debris.Order = string.Format("[{0}].{1} {2}", anlyTable.RName, tableInfo.PrimaryKey, System.Enum.GetName(typeof(OrderTypeEnum), OrderTypeEnum.ASC));
 33             }
 34             StringBuilder sb = new StringBuilder();
 35             sb.AppendFormat("[{0}] AS [{1}]", anlyTable.Name, anlyTable.RName);
 36             AppendLeftJoinTables(anlyTable, tableInfo, sb);
 37             Debris.From = sb.ToString();
 38         }
 39         public virtual void CreateWhere(AnalysisData data)
 40         {
 41             if (data == null || data.StackList.Count() <= 0)
 42             {
 43                 return;
 44             }
 45             Debris.Where = string.Join(" ", data.StackList);
 46         }
 47         public virtual void AppendSkip(int count)
 48         {
 49             Debris.Skip = count;
 50         }
 51         public virtual void AppendTake(int count)
 52         {
 53             Debris.Take = count;
 54         }
 55         public virtual void AppendOrder(AnalysisData data, OrderTypeEnum orderType)
 56         {
 57             if (data.StackList.Count <= 0)
 58             {
 59                 return;
 60             }
 61             var field = data.StackList.First();
 62             StringBuilder sb = new StringBuilder(Debris.Order);
 63             if (string.IsNullOrEmpty(Debris.Order))
 64             {
 65                 sb.AppendFormat("{0} {1}", field, System.Enum.GetName(typeof(OrderTypeEnum), orderType));
 66                 Debris.Order = sb.ToString();
 67                 return;
 68             }
 69             sb.AppendFormat(",{0} {1}", field, System.Enum.GetName(typeof(OrderTypeEnum), orderType));
 70             Debris.Order = sb.ToString();
 71         }
 72         public void GetCount()
 73         {
 74             Debris.Select = "COUNT(1)";
 75         }
 76 
 77         private KeyValuePair<string, TableInfo> GetForeignReference(TableInfo tInfo, string rName)
 78         {
 79             var keyValue = tInfo.ForeignRefs.Where(u => u.Value.RName == rName).FirstOrDefault();
 80             if (string.IsNullOrEmpty(keyValue.Key))
 81             {
 82                 foreach (var item in tInfo.ForeignRefs)
 83                 {
 84                     var foreignTable = GetForeignReference(item.Value, rName);
 85                     if (!string.IsNullOrEmpty(keyValue.Key))
 86                     {
 87                         return foreignTable;
 88                     }
 89                 }
 90 
 91             }
 92 
 93             return keyValue;
 94         }
 95         private void AppendLeftJoinTables(AnalysisTable anlyTable, TableInfo tableInfo, StringBuilder sb)
 96         {
 97             ///添加关系表信息
 98             foreach (var item in anlyTable.leftJoins)
 99             {
100                 var _foreignRef = GetForeignReference(tableInfo, item.RName);
101                 if (string.IsNullOrEmpty(_foreignRef.Key))
102                 {
103                     throw new BusinessException(BusinessRes.WhitoutThisForeignReference);
104                 }
105                 sb.AppendFormat(" LEFT JOIN [{0}] AS [{1}] ON [{1}].{2}=[{3}].{4} ", item.Name, item.RName, _foreignRef.Value.PrimaryKey, anlyTable.RName, _foreignRef.Key);
106                 AppendLeftJoinTables(item, _foreignRef.Value, sb);
107             }
108         }
109     }
View Code

  到此为止,生成的Sql依然是不完整的Sql碎片,我们需要将碎片合并成Sql语句,我写了一个工具类如下:

  保存碎片类:

 1 public class SqlDebris
 2     {
 3         public string Where { get; set; }
 4         #region 查询
 5         public string From { get; set; }
 6         public string Select { get; set; }
 7         public string Order { get; set; }
 8         public int Skip { get; set; }
 9         public int? Take { get; set; }
10         #endregion
11         #region 修改
12         public string Set { get; set; }
13         public string Update { get; set; }
14         #endregion
15         #region 新增
16 
17         public string InsertTable { get; set; }
18         public string Columns { get; set; }
19         public string Values { get; set; }
20 
21         #endregion
22     }
View Code

  通过下面工具类生成完整Sql语句:

 1 public class CommandTextHelper
 2     {
 3         public static string GetSelectCommandText(ICreator creator)
 4         {
 5             var debris = creator.Debris;
 6             StringBuilder sb = new StringBuilder();
 7             if (debris.Take == null)
 8             {
 9                 sb.Append(" SELECT * FROM (");
10             }
11             else
12             {
13                 //分页
14                 sb.AppendFormat(" SELECT TOP {0} * FROM (", debris.Take);
15             }
16             sb.AppendFormat("SELECT {0},ROW_NUMBER() OVER(ORDER BY  {2} ) as ROWNUMBER  FROM {1}", debris.Select, debris.From, debris.Order);
17             //条件
18             if (!string.IsNullOrEmpty(debris.Where))
19             {
20                 sb.AppendFormat(" WHERE {0} ", debris.Where);
21             }
22             sb.Append(") as NEW_TABLE ");
23             sb.AppendFormat(" WHERE ROWNUMBER>{0}", debris.Skip);
24             return sb.ToString();
25         }
26 
27         public static string GetCountCommandText(ICreator creator)
28         {
29             var debris = creator.Debris;
30             StringBuilder sb = new StringBuilder();
31 
32             sb.AppendFormat("SELECT {0} FROM {1}", debris.Select, debris.From);
33             //条件
34             if (!string.IsNullOrEmpty(debris.Where))
35             {
36                 sb.AppendFormat(" WHERE {0} ", debris.Where);
37             }
38             return sb.ToString();
39         }
40 
41         public static string GetUpdateCommandText(ICreator creator)
42         {
43             var debris = creator.Debris;
44             StringBuilder sb = new StringBuilder();
45             sb.AppendFormat("UPDATE {0} ", debris.Update);
46             sb.AppendFormat("SET {0} ", debris.Set);
47             sb.AppendFormat("WHERE {0}", debris.Where);
48             return sb.ToString();
49         }
50         public static string GetInsertCommandText(ICreator creator)
51         {
52             var debris = creator.Debris;
53             StringBuilder sb = new StringBuilder();
54             sb.AppendFormat("INSERT INTO {0}", debris.InsertTable);
55             sb.AppendFormat("({0}) ", debris.Columns);
56             sb.AppendFormat("VALUES({0})", debris.Values);
57             return sb.ToString();
58         }
59         public static string GetDeleteCommandText(ICreator creator)
60         {
61             var debris = creator.Debris;
62             StringBuilder sb = new StringBuilder();
63             sb.AppendFormat("DELETE FROM {0}", debris.From);
64             sb.AppendFormat(" WHERE {0}", debris.Where);
65             return sb.ToString();
66         }
67     }
View Code

  下面是CommandFactory<TEntity>:

1  public class CommandFactory<TEntity> 
2     {
3         protected TableInfo _tableInfo;
4         public CommandFactory()
5         {
6             _tableInfo = new Mapper().MapToTable(typeof(TEntity));
7         }
8     }
View Code

  

下面是QueryCommand<TEntity>实现:

 1  public class QueryCommand<TEntity> : CommandFactory<TEntity>
 2     {
 3         //sql碎片生成器
 4         private IQuery _creator;
 5         //查询参数
 6         private Dictionary<string, object> _params;
 7 
 8         private AnalysisTable _table;
 9 
10         public QueryCommand()
11         {
12             _creator = new QueryCreator();
13         }
14         public QueryCommand<TEntity> Where(Expression<Func<TEntity, bool>> exp)
15         {
16             var retData = new ExpressionAnalyzer(exp).GetAnalysisResult();
17             _creator.CreateWhere(retData);
18             _params = retData.ParamList;
19             _table = retData.Table;
20             return this;
21         }
22         public QueryCommand<TEntity> OrderBy(Expression<Func<TEntity, object>> exp)
23         {
24             _creator.AppendOrder(new ExpressionAnalyzer(exp).GetAnalysisResult(), OrderTypeEnum.ASC);
25             return this;
26         }
27         public QueryCommand<TEntity> OrderByDescding(Expression<Func<TEntity, object>> exp)
28         {
29             _creator.AppendOrder(new ExpressionAnalyzer(exp).GetAnalysisResult(), OrderTypeEnum.DESC);
30             return this;
31         }
32         public QueryCommand<TEntity> ThenBy(Expression<Func<TEntity, object>> exp)
33         {
34             return OrderBy(exp);
35         }
36         public QueryCommand<TEntity> ThenByDescding(Expression<Func<TEntity, object>> exp)
37         {
38             return OrderByDescding(exp);
39         }
40         public QueryCommand<TEntity> Skip(int count)
41<

以上是关于自己动手写ORM(02):Sql生成器实现的主要内容,如果未能解决你的问题,请参考以下文章

自己动手写ORM框架-java

自己动手写编译器:通过语法编译构建语法树并实现中间代码生成

自己动手写ORM的感受

死磕 java线程系列之自己动手写一个线程池(续)

死磕 java线程系列之自己动手写一个线程池(续)

python第四十五天 (SQLAlchemy) 的操作