“造轮运动”之 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)的方式进行操作。整体结构图如下:

下面就大致地介绍一下每一个模块的具体功能与实现的思路:
1、数据库连接管理(DBConnectionManagement)

数据库连接的管理实际上就是对数据库连接字符串与其对应的数据库连接对象的管理机制,它可以保证在进行一主多从的数据库部署时ORM帮助我们自动地切换连接的数据库,而且还支持 <最小使用>与 <轮询>两种数据库连接切换策略。
2、SQL命令执行入口(QueryExecute)

QueryExecute是CoffeeSQL生成的所有sql语句执行的入口,执行sql语句并返回结果,贯穿整个CoffeeSQL最核心的功能就是映射sql查询结果到实体,这里采用的是构建表达式树的技术,性能大大优于反射获取实体的方式,具体的两者速度对比的实验在7tiny的博客中有详细介绍,大家可以移步观看(https://www.cnblogs.com/7tiny/p/9861166.html),在我的博客(https://www.cnblogs.com/MaMaNongNong/p/12173620.html)中我使用表达式树的技术造了个简练版的OOM框架。
这里贴出核心代码,方便查看:
/// <summary>
/// Auto Fill Adapter
/// => Fill DataRow to Entity
/// </summary>
public class EntityFillAdapter<Entity>
{
private static readonly Func<DataRow, Entity> funcCache = GetFactory(); public static Entity AutoFill(DataRow row)
{
return funcCache(row);
} private static Func<DataRow, Entity> GetFactory()
{
#region get Info through Reflection
var entityType = typeof(Entity);
var rowType = typeof(DataRow);
var convertType = typeof(Convert);
var typeType = typeof(Type);
var columnCollectionType = typeof(DataColumnCollection);
var getTypeMethod = typeType.GetMethod("GetType", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string) }, null);
var changeTypeMethod = convertType.GetMethod("ChangeType", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(object), typeof(Type) }, null);
var containsMethod = columnCollectionType.GetMethod("Contains");
var rowIndexerGetMethod = rowType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(string) }, new[] { new ParameterModifier() });
var columnCollectionIndexerGetMethod = columnCollectionType.GetMethod("get_Item", BindingFlags.Instance | BindingFlags.Public, null, new[] { typeof(int) }, new[] { new ParameterModifier() });
var entityIndexerSetMethod = entityType.GetMethod("set_Item", BindingFlags.Instance | BindingFlags.NonPublic, null, new[] { typeof(string), typeof(object) }, null);
var properties = entityType.GetProperties(BindingFlags.Instance | BindingFlags.Public);
#endregion #region some Expression class that can be repeat used
//DataRow row
var rowDeclare = Expression.Parameter(rowType, "row");
//Student entity
var entityDeclare = Expression.Parameter(entityType, "entity");
//Type propertyType
var propertyTypeDeclare = Expression.Parameter(typeof(Type), "propertyType");
//new Student()
var newEntityExpression = Expression.New(entityType);
//row == null
var rowEqualnullExpression = Expression.Equal(rowDeclare, Expression.Constant(null));
//row.Table.Columns
var rowTableColumns = Expression.Property(Expression.Property(rowDeclare, "Table"), "Columns");
//int loopIndex
var loopIndexDeclare = Expression.Parameter(typeof(int), "loopIndex");
//row.Table.Columns[loopIndex].ColumnName
var columnNameExpression = Expression.Property(Expression.Call(rowTableColumns, columnCollectionIndexerGetMethod, loopIndexDeclare), "ColumnName");
//break;
LabelTarget labelBreak = Expression.Label();
//default(Student)
var defaultEntityValue = Expression.Default(entityType);
#endregion var setRowNotNullBlockExpressions = new List<Expression>(); #region entity = new Student();loopIndex = 0;
setRowNotNullBlockExpressions.Add(Expression.Assign(entityDeclare, newEntityExpression));
setRowNotNullBlockExpressions.Add(Expression.Assign(loopIndexDeclare, Expression.Constant())); #endregion #region loop Fill DataRow's field to Entity Indexer
/*
* while (true)
* {
* if (loopIndex < row.Table.Columns.Count)
* {
* entity[row.Table.Columns[loopIndex].ColumnName] = row[row.Table.Columns[loopIndex].ColumnName];
* loopIndex++;
* }
* else break;
* }
*/ setRowNotNullBlockExpressions.Add( Expression.Loop(
Expression.IfThenElse(
Expression.LessThan(loopIndexDeclare, Expression.Property(rowTableColumns, "Count")),
Expression.Block(
Expression.Call(entityDeclare, entityIndexerSetMethod, columnNameExpression, Expression.Call(rowDeclare, rowIndexerGetMethod, columnNameExpression)),
Expression.PostIncrementAssign(loopIndexDeclare)
),
Expression.Break(labelBreak)
),
labelBreak
)
);
#endregion #region assign for Entity property
foreach (var propertyInfo in properties)
{
var columnAttr = propertyInfo.GetCustomAttribute(typeof(ColumnAttribute), true) as ColumnAttribute; // no column , no translation
if (null == columnAttr) continue; if (propertyInfo.CanWrite)
{
var columnName = Expression.Constant(columnAttr.GetName(propertyInfo.Name), typeof(string)); //entity.Id
var propertyExpression = Expression.Property(entityDeclare, propertyInfo);
//row["Id"]
var value = Expression.Call(rowDeclare, rowIndexerGetMethod, columnName);
//default(string)
var defaultValue = Expression.Default(propertyInfo.PropertyType);
//row.Table.Columns.Contains("Id")
var checkIfContainsColumn = Expression.Call(rowTableColumns, containsMethod, columnName);
//!row["Id"].Equals(DBNull.Value)
var checkDBNull = Expression.NotEqual(value, Expression.Constant(System.DBNull.Value)); var propertyTypeName = Expression.Constant(propertyInfo.PropertyType.ToString(), typeof(string)); /*
* if (row.Table.Columns.Contains("Id") && !row["Id"].Equals(DBNull.Value))
* {
* propertyType = Type.GetType("System.String");
* entity.Id = (string)Convert.ChangeType(row["Id"], propertyType);
* }
* else
* entity.Id = default(string);
*/
setRowNotNullBlockExpressions.Add( Expression.IfThenElse(
Expression.AndAlso(checkIfContainsColumn, checkDBNull),
Expression.Block(
Expression.Assign(propertyTypeDeclare, Expression.Call(getTypeMethod, propertyTypeName)),
Expression.Assign(propertyExpression, Expression.Convert(Expression.Call(changeTypeMethod, value, propertyTypeDeclare), propertyInfo.PropertyType))
),
Expression.Assign(propertyExpression, defaultValue)
)
);
}
} #endregion var checkIfRowIsNull = Expression.IfThenElse(
rowEqualnullExpression,
Expression.Assign(entityDeclare, defaultEntityValue),
Expression.Block(setRowNotNullBlockExpressions)
); var body = Expression.Block( new[] { entityDeclare, loopIndexDeclare, propertyTypeDeclare },
checkIfRowIsNull,
entityDeclare //return Student;
); return Expression.Lambda<Func<DataRow, Entity>>(body, rowDeclare).Compile();
}
} #region
//public class Student : EntityDesign.EntityBase
//{
// [Column]
// public string Id { get; set; } // [Column("StudentName")]
// public string Name { get; set; }
//}
////this is the template of "GetFactory()" created.
//public static Student StudentFillAdapter(DataRow row)
//{
// Student entity;
// int loopIndex;
// Type propertyType; // if (row == null)
// entity = default(Student);
// else
// {
// entity = new Student();
// loopIndex = 0; // while (true)
// {
// if (loopIndex < row.Table.Columns.Count)
// {
// entity[row.Table.Columns[loopIndex].ColumnName] = row[row.Table.Columns[loopIndex].ColumnName];
// loopIndex++;
// }
// else break;
// } // if (row.Table.Columns.Contains("Id") && !row["Id"].Equals(DBNull.Value))
// {
// propertyType = Type.GetType("System.String");
// entity.Id = (string)Convert.ChangeType(row["Id"], propertyType);
// }
// else
// entity.Id = default(string); // if (row.Table.Columns.Contains("StudentName") && !row["StudentName"].Equals(DBNull.Value))
// {
// propertyType = Type.GetType("System.String");
// entity.Name = (string)Convert.ChangeType(row["StudentName"], propertyType);
// }
// else
// entity.Name = default(string);
// } // return entity;
//}
#endregion
EntityFillAdapter(表达式树技术)
3、SQL查询引擎(QueryEngine)

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

实体数据验证是完全独立的一部分,主要用来检验实体类中字段值的合法性,相当于在高级语言层面对即将持久化到数据库表中的数据进行预先的字段合法性校验,避免在持久化过程中发生不必要的字段格式不合法的错误。
5、ORM缓存机制(ORMCache)

这里的ORM缓存主要分为两级缓存,一级缓存为以sql语句为缓存键的缓存,缓存的内容就是当前执行的sql语句的执行结果;而二级缓存则是以表名为缓存键的表缓存,就是会把一整个表的数据全部存入缓存中,所以表缓存最适合那些数据量不大且查询频繁的表。
6、SQL命令生成器【强类型】(CommandTextGenerator)

在使用诸如强类型查询引擎、Update执行引擎等进行了强类型的SQL语句构造后,相应的sql构造信息都要通过SQL命令生成器来生成最终可由数据库执行的sql语句。SQL命令生成器扮演的就是类似于翻译官的角色,将高级语言中的语句转化为数据库中的sql语句。在实际的应用场景中还可以根据不同的数据库类型将SQL命令生成器扩展成诸如Mysql-SQL命令生成器或者Oracle-SQL命令生成器以符合不同类型数据库的不同sql语法。
7、数据库上下文(DBContext)

作为整个CoffeeSQL的操作入口,DBContext类涵盖了各种配置参数字段与增删改查的API调用函数。其中在事务处理中,由于写操作都是通过对主库的操作,所以在事务处理中是以主库作为事务处理的对象。
二、使用方式
下载CoffeeSql源码进行编译,你会得到 CoffeeSql.Core.dll、CoffeeSql.Oracle.dll、CoffeeSql.Mysql.dll 三个dll文件,其中CoffeeSql.Core.dll为必选,然后根据你的数据库类型选择是CoffeeSql.Oracle.dll或者CoffeeSql.Mysql.dll,目前还只支持这两种数据库,后续会支持更多数据库。

三、展望
路漫漫其修远兮,吾将上下而求索,对比市面上火热的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
“造轮运动”之 ORM框架系列(三)~ 干货呈上的更多相关文章
- “造轮运动”之 ORM框架系列(二)~ 说说我心目中的ORM框架
ORM概念解析 首先梳理一下ORM的概念,ORM的全拼是Object Relation Mapping (对象关系映射),其中Object就是面向对象语言中的对象,本文使用的是c#语言,所以就是.ne ...
- “造轮运动”之 ORM框架系列(一)~谈谈我在实际业务中的增删改查
想想毕业已经快一年了,也就是大约两年以前,怀着满腔的热血正式跨入程序员的世界,那时候的自己想象着所热爱的技术生涯会是多么的丰富多彩,每天可以与大佬们坐在一起讨论解决各种牛逼的技术问题,喝着咖啡,翘着二 ...
- ORM框架示例及查询测试,上首页修改版(11种框架)
继上次ORM之殇,我们需要什么样的ORM框架? 整理了11个ORM框架测试示例,通过示例代码和结果,能很容易了解各种框架的特性,优缺点,排名不分先后 EF PDF XCODE CRL NHiberna ...
- Quartz.NET开源作业调度框架系列(三):IJobExecutionContext 参数传递
前面写了关于Quartz.NET开源作业调度框架的入门和Cron Trigger , 这次继续这个系列, 这次想讨论一下Quartz.NET中的Job如何通过执行上下文(Execution Conte ...
- Quartz.NET开源作业调度框架系列(三):IJobExecutionContext 参数传递-转
前面写了关于Quartz.NET开源作业调度框架的入门和Cron Trigger , 这次继续这个系列, 这次想讨论一下Quartz.NET中的Job如何通过执行上下文(Execution Conte ...
- Spring框架系列(三)--Bean的作用域和生命周期
Bean的作用域 Spring应用中,对象实例都是在Container中,负责创建.装配.配置和管理生命周期(new到finalize()) Spring Container分为两种: 1.BeanF ...
- 手撸ORM浅谈ORM框架之Add篇
快速传送 手撸ORM浅谈ORM框架之基础篇 手撸ORM浅谈ORM框架之Add篇 手撸ORM浅谈ORM框架之Update篇 手撸ORM浅谈ORM框架之Delete篇 手撸ORM浅谈ORM框架之Query ...
- 手撸ORM浅谈ORM框架之Query篇
快速传送 手撸ORM浅谈ORM框架之基础篇 手撸ORM浅谈ORM框架之Add篇 手撸ORM浅谈ORM框架之Update篇 手撸ORM浅谈ORM框架之Delete篇 手撸ORM浅谈ORM框架之Query ...
- Quartz.NET开源作业调度框架系列
Quartz.NET是一个被广泛使用的开源作业调度框架 , 由于是用C#语言创建,可方便的用于winform和asp.net应用程序中.Quartz.NET提供了巨大的灵活性但又兼具简单性.开发人员可 ...
随机推荐
- SpringMVC 设置全局DateTime json返回格式
对于部分返回DateTime的项目,只需要在指定属性上添加@JsonSerialize 使用自定义的json转换格式即可自定义返回DateTime格式 但是对于项目中返回有多个DateTime字段来说 ...
- PHP EOF使用说明
PHP EOF(heredoc) 使用说明 PHP EOF(heredoc)是一种在命令行shell(如sh.csh.ksh.bash.PowerShell和zsh)和程序语言(像Perl.PHP.P ...
- react-grid-layout实现拖拽,网格布局
借鉴地址:https://www.jianshu.com/p/b48858eee3a7 安装 react-grid-layout npm install react-grid-layout impor ...
- 「雕爷学编程」Arduino动手做(37)——MQ-3酒精传感器
37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是不止37种的.鉴于本人手头积累了一些传感器和模块,依照实践出真知(一定要动手做)的理念,以学习和交流为目的,这里 ...
- 读懂这几个关键词,你就能了解 Docker 啦
基于高度虚拟化所诞生的容器技术,如今已经走向大规模应用.那么容器.虚拟机.Docker.Openstack.Kubernetes 之间又有什么关系,对现在的选择有什么影响呢? 上世纪 60 年代,计算 ...
- vim命令备份
vim命令 vim键盘位置说明 在命令状态下对当前行用 == (连按=两次), 或对多行用 n==(n是自然数)表示自动缩进从当前行起的下面n行. 可以试试把代码缩进任意打乱再用 n== 排版,相当于 ...
- 【python深度学习】KS,KL,JS散度 衡量两组数据是否同分布
目录 KS(不需要两组数据相同shape) JS散度(需要两组数据同shape) KS(不需要两组数据相同shape) 奇怪之处:有的地方也叫KL KS距离,相对熵,KS散度 当P(x)和Q(x)的相 ...
- Kappa(cappa)系数只需要看这一篇就够了,算法到python实现
1 定义 百度百科的定义: 它是通过把所有地表真实分类中的像元总数(N)乘以混淆矩阵对角线(Xkk)的和,再减去某一类地表真实像元总数与被误分成该类像元总数之积对所有类别求和的结果,再除以总像元数的平 ...
- kubernetes flannel pod CrashLoopBackoff解决
背景 某环境客户部署了一个kubernetes集群,发现flannel的pod一直重启,始终处于CrashLoopBackOff状态. 排查 对于始终CrashLoopBackOff的pod,一般是应 ...
- pyenv,轻松切换各种python版本
pyenv,轻松切换各种python版本 解决什么问题 mac自带python2,md又不能删掉他 linux也自带python2,这玩意都过时了,也不赶紧换掉 安装pyenv git 安装 git ...