“造轮运动”之 ORM框架系列(三)~ 干货呈上

   这一趴内里,我就来正式先容一下CoffeeSQL的干货。

    首先要给CoffeeSQL来个定位:最最先就是由于本人想要领会ORM框架内部的原理,以是就四处征采有关的博客与学习资料,就是在谁人炎天,在博客园上看到了一位7tiny老哥的博客(https://www.cnblogs.com/7tiny/p/9575230.html),内里基本上包含了我所想要领会的全套内容。幸得7tiny老哥的博客和代码都写的异常清晰,以是没花多久时间就看完了源码并洞悉其中奇妙,于是自己就有个想法:在7tiny的开源代码的基础上归纳自己的ORM框架。于是出于学习与自我使用的目的就最先了扩展功效的门路,到现在为止,自己已经在公司的一个项目中用上了,效果还不错。在这里也谢谢7tiny老哥对我提出的一些问题实时的回复和指导,至心谢谢。

一、框架模块先容

  凭据CoffeeSQL的功效模块组成来划分,可以分为:数据库毗邻治理、SQL下令执行入口、SQL下令天生器、SQL查询引擎、ORM缓存机制、实体数据验证 这六个部门,CoffeeSQL的操作入口与其他的ORM框架一样,都是以数据库上下文(DBContext)的方式举行操作。整体结构图如下:

“造轮运动”之 ORM框架系列(三)~ 干货呈上

 

下面就大致地先容一下每一个模块的详细功效与实现的思绪:

1、数据库毗邻治理(DBConnectionManagement)

“造轮运动”之 ORM框架系列(三)~ 干货呈上

   数据库毗邻的治理现实上就是对数据库毗邻字符串与其对应的数据库毗邻工具的治理机制,它可以保证在举行一主多从的数据库部署时ORM辅助我们自动地切换毗邻的数据库,而且还支持 <最小使用>与 <轮询>两种数据库毗邻切换计谋。

 

2、SQL下令执行入口(QueryExecute)

“造轮运动”之 ORM框架系列(三)~ 干货呈上

   QueryExecute是CoffeeSQL天生的所有sql语句执行的入口,执行sql语句并返回效果,贯串整个CoffeeSQL最焦点的功效就是映射sql查询效果到实体,这里接纳的是构建表达式树的手艺,性能大大优于反射获取实体的方式,详细的两者速率对比的实验在7tiny的博客中有详细先容,人人可以移步旁观(https://www.cnblogs.com/7tiny/p/9861166.html),在我的博客(https://www.cnblogs.com/MaMaNongNong/p/12173620.html)中我使用表达式树的手艺造了个精练版的OOM框架。

   这里贴出焦点代码,利便查看:

   
“造轮运动”之 ORM框架系列(三)~ 干货呈上

  1     /// <summary>
  2     /// Auto Fill Adapter
  3     /// => Fill DataRow to Entity
  4     /// </summary>
  5     public class EntityFillAdapter<Entity>
  6     {
  7         private static readonly Func<DataRow, Entity> funcCache = GetFactory();
  8 
  9         public static Entity AutoFill(DataRow row)
 10         {
 11             return funcCache(row);
 12         }
 13 
 14         private static Func<DataRow, Entity> GetFactory()
 15         {
 16             #region get Info through Reflection
 17             var entityType = typeof(Entity);
 18             var rowType = typeof(DataRow);
 19             var convertType = typeof(Convert);
 20             var typeType = typeof(Type);
 21             var columnCollectionType = typeof(DataColumnCollection);
 22             var getTypeMethod = typeType.GetMethod("GetType", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string) }, null);
 23             var changeTypeMethod = convertType.GetMethod("ChangeType", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(object), typeof(Type) }, null);
 24             var containsMethod = columnCollectionType.GetMethod("Contains");
 25             var rowIndexerGetMethod = rowType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(string) }, new[] { new ParameterModifier(1) });
 26             var columnCollectionIndexerGetMethod = columnCollectionType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(int) }, new[] { new ParameterModifier(1) });
 27             var entityIndexerSetMethod = entityType.GetMethod("set_Item", BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(string), typeof(object) }, null);
 28             var properties = entityType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
 29             #endregion
 30 
 31             #region some Expression class that can be repeat used
 32             //DataRow row
 33             var rowDeclare = Expression.Parameter(rowType, "row");
 34             //Student entity
 35             var entityDeclare = Expression.Parameter(entityType, "entity");
 36             //Type propertyType
 37             var propertyTypeDeclare = Expression.Parameter(typeof(Type), "propertyType");
 38             //new Student()
 39             var newEntityExpression = Expression.New(entityType);
 40             //row == null
 41             var rowEqualnullExpression = Expression.Equal(rowDeclare, Expression.Constant(null));
 42             //row.Table.Columns
 43             var rowTableColumns = Expression.Property(Expression.Property(rowDeclare, "Table"), "Columns");
 44             //int loopIndex
 45             var loopIndexDeclare = Expression.Parameter(typeof(int), "loopIndex");
 46             //row.Table.Columns[loopIndex].ColumnName
 47             var columnNameExpression = Expression.Property(Expression.Call(rowTableColumns, columnCollectionIndexerGetMethod, loopIndexDeclare), "ColumnName");
 48             //break;
 49             LabelTarget labelBreak = Expression.Label();
 50             //default(Student)
 51             var defaultEntityValue = Expression.Default(entityType);
 52             #endregion
 53 
 54             var setRowNotNullBlockExpressions = new List<Expression>();
 55                         
 56             #region entity = new Student();loopIndex = 0;
 57             setRowNotNullBlockExpressions.Add(Expression.Assign(entityDeclare, newEntityExpression));
 58             setRowNotNullBlockExpressions.Add(Expression.Assign(loopIndexDeclare, Expression.Constant(0)));
 59 
 60             #endregion
 61 
 62             #region loop Fill DataRow's field to Entity Indexer
 63             /*
 64              * while (true)
 65              * {
 66              *     if (loopIndex < row.Table.Columns.Count)
 67              *     {
 68              *         entity[row.Table.Columns[loopIndex].ColumnName] = row[row.Table.Columns[loopIndex].ColumnName];
 69              *         loopIndex++;
 70              *     }
 71              *     else break;
 72              * } 
 73              */
 74 
 75             setRowNotNullBlockExpressions.Add(
 76 
 77                 Expression.Loop(
 78                     Expression.IfThenElse(
 79                         Expression.LessThan(loopIndexDeclare, Expression.Property(rowTableColumns, "Count")),
 80                         Expression.Block(
 81                             Expression.Call(entityDeclare, entityIndexerSetMethod, columnNameExpression, Expression.Call(rowDeclare, rowIndexerGetMethod, columnNameExpression)),
 82                             Expression.PostIncrementAssign(loopIndexDeclare)
 83                         ),
 84                         Expression.Break(labelBreak)
 85                     ),
 86                     labelBreak
 87                 )
 88             );
 89             #endregion
 90 
 91             #region assign for Entity property
 92             foreach (var propertyInfo in properties)
 93             {
 94                 var columnAttr = propertyInfo.GetCustomAttribute(typeof(ColumnAttribute), true) as ColumnAttribute;
 95 
 96                 // no column , no translation
 97                 if (null == columnAttr) continue;
 98 
 99                 if (propertyInfo.CanWrite)
100                 {
101                     var columnName = Expression.Constant(columnAttr.GetName(propertyInfo.Name), typeof(string));
102 
103                     //entity.Id
104                     var propertyExpression = Expression.Property(entityDeclare, propertyInfo);
105                     //row["Id"]
106                     var value = Expression.Call(rowDeclare, rowIndexerGetMethod, columnName);
107                     //default(string)
108                     var defaultValue = Expression.Default(propertyInfo.PropertyType);
109                     //row.Table.Columns.Contains("Id")
110                     var checkIfContainsColumn = Expression.Call(rowTableColumns, containsMethod, columnName);
111                     //!row["Id"].Equals(DBNull.Value)
112                     var checkDBNull = Expression.NotEqual(value, Expression.Constant(System.DBNull.Value));
113                     
114                     var propertyTypeName = Expression.Constant(propertyInfo.PropertyType.ToString(), typeof(string));
115 
116                     /*
117                      * if (row.Table.Columns.Contains("Id") && !row["Id"].Equals(DBNull.Value))
118                      * {
119                      *     propertyType = Type.GetType("System.String");
120                      *     entity.Id = (string)Convert.ChangeType(row["Id"], propertyType);
121                      * }
122                      * else
123                      *     entity.Id = default(string);
124                      */
125                     setRowNotNullBlockExpressions.Add(
126 
127                         Expression.IfThenElse(
128                             Expression.AndAlso(checkIfContainsColumn, checkDBNull),
129                             Expression.Block(
130                                 Expression.Assign(propertyTypeDeclare, Expression.Call(getTypeMethod, propertyTypeName)),
131                                 Expression.Assign(propertyExpression, Expression.Convert(Expression.Call(changeTypeMethod, value, propertyTypeDeclare), propertyInfo.PropertyType))
132                             ),
133                             Expression.Assign(propertyExpression, defaultValue)
134                         )
135                     );
136                 }
137             }
138 
139             #endregion
140 
141             var checkIfRowIsNull = Expression.IfThenElse(
142                 rowEqualnullExpression,
143                 Expression.Assign(entityDeclare, defaultEntityValue),
144                 Expression.Block(setRowNotNullBlockExpressions)
145             );
146 
147             var body = Expression.Block(
148 
149                 new[] { entityDeclare, loopIndexDeclare, propertyTypeDeclare },
150                 checkIfRowIsNull,
151                 entityDeclare   //return Student;
152             );
153 
154             return Expression.Lambda<Func<DataRow, Entity>>(body, rowDeclare).Compile();
155         }
156     }
157 
158     #region
159     //public class Student : EntityDesign.EntityBase
160     //{
161     //    [Column]
162     //    public string Id { get; set; }
163 
164     //    [Column("StudentName")]
165     //    public string Name { get; set; }
166     //}
167     ////this is the template of "GetFactory()" created.
168     //public static Student StudentFillAdapter(DataRow row)
169     //{
170     //    Student entity;
171     //    int loopIndex;
172     //    Type propertyType;
173 
174     //    if (row == null)
175     //        entity = default(Student);
176     //    else
177     //    {
178     //        entity = new Student();
179     //        loopIndex = 0;
180 
181     //        while (true)
182     //        {
183     //            if (loopIndex < row.Table.Columns.Count)
184     //            {
185     //                entity[row.Table.Columns[loopIndex].ColumnName] = row[row.Table.Columns[loopIndex].ColumnName];
186     //                loopIndex++;
187     //            }
188     //            else break;
189     //        }
190 
191     //        if (row.Table.Columns.Contains("Id") && !row["Id"].Equals(DBNull.Value))
192     //        {
193     //            propertyType = Type.GetType("System.String");
194     //            entity.Id = (string)Convert.ChangeType(row["Id"], propertyType);
195     //        }
196     //        else
197     //            entity.Id = default(string);
198 
199     //        if (row.Table.Columns.Contains("StudentName") && !row["StudentName"].Equals(DBNull.Value))
200     //        {
201     //            propertyType = Type.GetType("System.String");
202     //            entity.Name = (string)Convert.ChangeType(row["StudentName"], propertyType);
203     //        }
204     //        else
205     //            entity.Name = default(string);
206     //    }
207 
208     //    return entity;
209     //}
210     #endregion

EntityFillAdapter(表达式树手艺)

 

3、SQL查询引擎(QueryEngine)

“造轮运动”之 ORM框架系列(三)~ 干货呈上

  SQL查询引擎的功效主要就是以函数的形式来构建查询SQL的结构。将sql语句使用高级语言的函数来举行构建能大大减轻程序员必须一丝不苟编写sql语句的压力。特别是在使用强类型查询引擎时以Lambda表达式的方式编写程序,相当恬静的体验;对于稍微庞大的sql,建议使用弱类型查询引擎来构建sql查询语句,同时也提供利便的分页功效,用法与Dapper类似;再庞大一点的数据库查询逻辑可能你就要思量使用存储历程查询引擎了,总之,有了这三个查询引擎,所有的查询需求都能知足了。最后一个是update的执行引擎,它被用来构建update的语句。

 

4、实体数据验证(EntityValidation)

“造轮运动”之 ORM框架系列(三)~ 干货呈上

  实体数据验证是完全自力的一部门,主要用来磨练实体类中字段值的合法性,相当于在高级语言层面临即将持久化到数据库表中的数据举行预先的字段合法性校验,制止在持久化历程中发生不必要的字段花样不合法的错误。

 

5、ORM缓存机制(ORMCache)

“造轮运动”之 ORM框架系列(三)~ 干货呈上

tab-switch 样式的添加 与 tab元素样式的切换

  这里的ORM缓存主要分为两级缓存,一级缓存为以sql语句为缓存键的缓存,缓存的内容就是当前执行的sql语句的执行效果;而二级缓存则是以表名为缓存键的表缓存,就是会把一整个表的数据所有存入缓存中,以是表缓存最适合那些数据量不大且查询频仍的表

 

6、SQL下令天生器【强类型】(CommandTextGenerator)

“造轮运动”之 ORM框架系列(三)~ 干货呈上

  在使用诸如强类型查询引擎、Update执行引擎等举行了强类型的SQL语句组织后,响应的sql组织信息都要通过SQL下令天生器来天生最终可由数据库执行的sql语句。SQL下令天生器饰演的就是类似于翻译官的角色,将高级语言中的语句转化为数据库中的sql语句。在现实的应用场景中还可以凭据差别的数据库类型将SQL下令天生器扩展成诸如Mysql-SQL下令天生器或者Oracle-SQL下令天生器以相符差别类型数据库的差别sql语法。

 

7、数据库上下文(DBContext)

“造轮运动”之 ORM框架系列(三)~ 干货呈上

  作为整个CoffeeSQL的操作入口,DBContext类涵盖了种种设置参数字段与增删改查的API挪用函数。其中在事务处理中,由于写操作都是通过对主库的操作,以是在事务处理中是以主库作为事务处理的工具。

二、使用方式

  下载CoffeeSql源码举行编译,你会获得 CoffeeSql.Core.dll、CoffeeSql.Oracle.dll、CoffeeSql.Mysql.dll 三个dll文件,其中CoffeeSql.Core.dll为必选,然后凭据你的数据库类型选择是CoffeeSql.Oracle.dll或者CoffeeSql.Mysql.dll,现在还只支持这两种数据库,后续会支持更多数据库。

“造轮运动”之 ORM框架系列(三)~ 干货呈上

 

 

三、展望

  路漫漫其修远兮,吾将上下而求索,对比市面上火热的ORM框架,CoffeeSQL照样缺少了一些适用的功效,对这个ORM框架的展望中我会思量以下一些功效:

    1、CodeFirst、DbFirst功效的支持,可以快捷利便地举行实体类与数据库建表sql的天生;

    2、批量插入操作的实现,可以提高批量插入数据的性能;

    3、对多表团结查询的lambda语法支持;

  

  先容的再多都不如读一遍源码来的着实,有想深入领会orm原理的小伙伴可以阅读一下源码,真的SO EASY!

   源码地址:https://gitee.com/xiaosen123/CoffeeSqlORM

   本文为作者原创,转载请注明出处:https://www.cnblogs.com/MaMaNongNong/p/12896787.html

 

原创文章,作者:admin,如若转载,请注明出处:https://www.2lxm.com/archives/14176.html