SOD框架的数据容器,打造最适合DDD的ORM框架
SOD框架的数据容器,打造最适合DDD的ORM框架
引言:DDD的困惑
最近,我看到园子里面有位朋友的一篇博客 《领域驱动设计系列(一):为何要领域驱动设计? 》文章中有下面一段话,对DDD使用产生的疑问:
•没有正确的使用ORM, 导致数据加载过多,导致系统性能很差。 •为了解决性能问题,就不加载一些导航属性,但是却把DB Entity返回上层,这样对象的一些属性为空,上层使用这个数据时根本不知道什么时间这个属性是有值的,这个是很丑陋的是不是?
博主说的第一个问题,是因为使用ORM的人把实体类的全部属性的数据查询出来了,相当于执行了 select * from table 这样的查询,而实际上,Domain层是不需要这么多额外的数据的。
重新定义一个Domain需要的 DTO? 但这又会导致DTO膨胀,DTO对象满天飞!
所以为了简便,就直接查询出全部属性对应的数据,或者也用EF的Select子句,投影下,但将结果又投影给了另外一个DTO对象或者Entity 对象,这样就使得对象中部分属性为空了,于是又产生了博主的第二个问题。
第二个问题有多严重?
假设某个表有50个字段,这样大的表在很多复杂的系统中是很常见的,于是MAP出来的Entity或者DTO,也有50个属性,而我这次仅需要使用其中的2个属性的值,于是,这个对象上的 48个属性数据都浪费了。
如果这样的DTO对象用在List上且用于分布式环境,那么,这样浪费的网络IO和序列化,凡序列化浪费的CPU,还是比较严重的。
1,准备工作
比如有下面一个用户信息类接口:
public interface IUser
{
int Age { get; set; }
string FirstName { get; set; }
string LasttName { get; set; }
int UserID { get; set; }
}
然后根据这个接口,写一个PDF.NET SOD 实体类 UserEntity ,用于持久化数据到数据库或者其它用途:
还有一个用户类的DTO类 UserDto,可用于分布式系统的数据传输或者解决方案多个项目分层之间的数据传输:
2,SOD框架的实体类
2.1,索引器访问与字段映射
如果 UserEntity user=new UserEntity();此时user 对象里面并没有 UserID 的数据,除非调用了属性的Set方法,此时,可以用下面的代码来验证:
UserEntity user=new UserEntity(); bool flag=(user["User ID"] ==null);//true
注意 user["User ID"] 这个地方,SOD的实体类可以当作“索引器”来使用,索引器的Key是实体类属性Map的数据库字段名称,请看UserEntity. UserID 属性的定义:
public int UserID
{
get { return getProperty<int>("User ID"); }
set { setProperty("User ID", value); }
}
可见我们可以将一个不同的字段名影射到一个属性名上。所以,根据这个定义,访问索引器 user["User ID"] 就等于访问 user实体类的属性 UserID 。
2.2,“空”的两种境界(null / DBNull.Value)
从这里我们可以得出结论:
结论一:
SOD 实体类的属性值默认均为空 (null)
2.2.1,程序中的 null
此时的空,代表数据没有作任何初始化,这种“空”来自以程序中。我们还可以通过查询来进一步验证这种情况的空值:
假如我们的ORM查询语言OQL查询并没有指定要查询实体类的Age属性,那么结果user对象仅有2个数据,并没有3个数据:
OQL q3 = OQL.From(uq)
.Select(uq.UserID, uq.FirstName) //未查询 user.Age 字段
.Where(uq.FirstName)
.END;
UserEntity user3 = context.UserQuery.GetObject(q3);
//未查询 user.Age 字段,此时查询该字段的值应该是 null
bool flag3 = (user3["Age"] == null);//true
Console.WriteLine("user[\"Age\"] == null :{0}", flag);
Console.WriteLine("user.Age:{0}", user3.Age);
程序输出:
user["Age"] == null :True
user.Age:0
2.2.2,数据库中的 NULL
为了验证SOD 实体类从数据库查询出来的字段的空值是什么情况,我们先插入几条测试数据:
LocalDbContext context = new LocalDbContext();//自动创建表 //插入几条测试数据
context.Add<UserEntity>(new UserEntity() { FirstName ="zhang", LasttName="san" });
context.Add<IUser>(new UserDto() { FirstName = "li", LasttName = "si", Age = 21 });
context.Add<IUser>(new UserEntity() { FirstName = "wang", LasttName = "wu", Age = 22 });
我们插入的第一条数据并没有年龄Age 的数据,下面再来查询这条数据,看数据库的值是否为NULL:
//查找姓张的一个用户
UserEntity uq = new UserEntity() { FirstName = "zhang" };
OQL q = OQL.From(uq)
.Select(uq.UserID, uq.FirstName, uq.Age)
.Where(uq.FirstName)
.END; //下面的语句等效
//UserEntity user2 = EntityQuery<UserEntity>.QueryObject(q,context.CurrentDataBase);
UserEntity user2 = context.UserQuery.GetObject(q);
//zhang san 的Age 未插入值,此时查询该字段的值应该是 NULL
bool flag2 = (user2["Age"] == DBNull.Value);//true
Console.WriteLine("user[\"Age\"] == DBNULL.Value :{0}", flag);
注意,这里我们在OQL的Select 子句中,指定了要查询实体类的 Age 属性,如果数据库没有该属性字段的值,它一定是NULL,也就是 程序中说的 NBNULL.Value,看输出结果验证:
user["Age"] == DBNULL.Value :True
user.Age:0
当然,这里数据库为空,要求表字段是支持可空的。
从这里我们可以得出结论:
结论二: SOD 用OQL 查询的实体类属性,如果数据库对应的字段值为空,那么实体类内部该属性值也为空(DBNull.Value)
2.3,可空类型的问题
在EF等ORM中,要定义一个字段可空,需要定义成可空类型,比如我们的User类,假设定义成EF的实体类,应该是这样子的:
public class EFUserEntity
{
int? Age { get; set; }
[MaxLength(20)]
string? FirstName { get; set; }
[MaxLength(10)]
string? LasttName { get; set; }
[Key]
[Required]
int UserID { get; set; } //主键,不可为空
}
这种可空类型的实体类定义,能够让数据库字段标记为NULL,但是,这个实体类在于DTO类进行转换的时候,总会遇到一些麻烦,因为实体类属性为空,而DTO属性不为空。
有人说,我们把DTO属性也定义为可空类型,不就好了么?
我在想,.NET推出值类型上的可空类型,本意是为了兼容从数据库来的空值,这样,对于 int a; 这个变量来说,可以知道它的值到底是0,还是变量根本没有值,这是未知的,而int? a; 这个变量完美的解决了这个问题。
但是,如果你的服务的客户端不是.net,而是JAVA,JS,或者其它不支持可空类型的语言,这种有可空类型属性的DTO就遇上麻烦了。
所以,SOD的实体类,属性可以定义为非可空类型的,但是属性的内部值,null或者 DBNull.Value 都是可以的。
3,数据的容器
SOD实体类可以仅看作一个数据容器,又可以看作一个ORM的实体类,大大增加了使用的灵活性和查询的效率。
对于上面的查询,不管Age属性在实体类里面是
bool flag=(user2["Age"]==NBNull.Value);//true
还是
bool flag=(user3["Age"]==null);//true
当外面获取Age属性的时候,都是Age的默认值0:
int age=user2.Age;//0
int age=user3.Age;//0
这些数据在实体类中是怎么存储的呢?原来,实体类内部有一个类似于“名-值对”的2个数组,用于存储实体类映射的数据库字段名和字段的值,这个结构就是SOD框架的中的 PropertyNameValues 类,定义很简单:
public class PropertyNameValues
{
public string[] PropertyNames { get; set; }
public object[] PropertyValues { get; set; }
}
所以实体类的字段值是存储在Object对象上,这也是 为何SOD实体类可以处理2种空值null,DBNull.Value的原因。当然你也可以存其它内容,只要属性类型兼容即可。比如属性类型是long,而数据库字段的值类型是 int ,这在SOD实体类是允许的。
另外,这个值的可变性,使得SOD框架处理 枚举属性 非常方便,因为,Enum 与int 类型是兼容的,可以相互转换,参看这篇文章:
《 实体类的枚举属性--原来支持枚举类型这么简单,没有EF5.0也可以》
属性值的可变性,除了上面的好处,还有什么好处?
好处大大的,这意味着 PropertyNames,PropertyValues 的长度是可变的,就像前面的例子,查询了Age属性,实体类的值有3个,而不查询,那么值只有2个。
假设实体类有50个属性,本次只查询了2个属性,那么SOD的实体类实际传输的数据就只有2个,而不是50个,这将大大节省数据传输量。
这个可以通过SOD实体类的序列化结果来验证。
4,在分布式系统上使用实体类
4.1,实体类的序列化与反序列化
这里必然绕不开实体类的序列化与反序列化,现在最新的SOD框架已经内置支持,参考下面的代码:
//查找姓张的一个用户
UserEntity uq = new UserEntity() { FirstName = "zhang" }; OQL q3 = OQL.From(uq)
.Select(uq.UserID, uq.FirstName) //未查询 user.Age 字段
.Where(uq.FirstName)
.END;
UserEntity user3 = context.UserQuery.GetObject(q3); Console.WriteLine("实体类序列化测试");
var entityNameValues= user3.GetNameValues();
PropertyNameValuesSerializer ser = new PropertyNameValuesSerializer(entityNameValues);
string strEntity = ser.Serializer();
Console.WriteLine(strEntity);
Console.WriteLine("成功");
//
Console.WriteLine("反序列化测试");
PropertyNameValuesSerializer des = new PropertyNameValuesSerializer(null);
UserEntity desUser = des.Deserialize<UserEntity>(strEntity);
Console.WriteLine("成功");
下面是序列化结果的输出:
<?xml version="1.0" encoding="utf-16"?>
<PropertyNameValues xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<PropertyNames>
<string>User ID</string>
<string>First Name</string>
</PropertyNames>
<PropertyValues>
<anyType xsi:type="xsd:int">26</anyType>
<anyType xsi:type="xsd:string">zhang</anyType>
</PropertyValues>
</PropertyNameValues>
可见,以这种方式序列化传输的数据量,将是很少的。当然,你还可以更改成JSOn序列化,这样数据更少,缺点是数据元数据没有了。
4.2,Entity,DomainModel,DTO 之间的数据拷贝
三层或者多层架构,或者DDD架构,少不了Entity,DomainModel,DTO 之间的数据拷贝,如果数据结构高度相似,可以使用AutoMapper之类的工具,而在SOD框架内,使用了速度最快的属性拷贝方案,参见之前我写的博客文章:
《使用反射+缓存+委托,实现一个不同对象之间同名同类型属性值的快速拷贝》
另外,如果是从实体类到DTO,或者DTO到实体类的数据复制,在EntityBase上提供了 MapFrom和MapTo方法,例如下面使用的例子:
IUser TestMapFromDTO(IUser data)
{
IUser user = EntityBuilder.CreateEntity<IUser>();
((entityBase)user).MapFrom(data);
return user;
}
当然,还有CopyTo方法,只要你引用了框架扩展 PWMIS.Core.Extension.dll
using PWMIS.Core.Extensions;
... ... //CoyTo 创建一个实例对象
ImplCarInfo icResult= info.CopyTo<ImplCarInfo>(null); //CopyTo 填充一个实例对象
ImplCarInfo icResult2 = new ImplCarInfo();
info.CopyTo<ImplCarInfo>(icResult2);
将实体类的数据拷贝到DTO对象的时候,推荐下面这种直接调用 这种方式:
DTOXXX dto=EntityObject.CopyTo<DTOXX>();
5,SOD框架 的CodeFirst支持
最新版的SOD框架(PDF.NET SOD)已经可以方便的支持CodeFirst开发了,使用很简单,调用只需要一行代码:
Console.WriteLine("第一次运行,将检查并创建数据表");
LocalDbContext context = new LocalDbContext();//自动创建表
而这个LocalDbContext 的定义也不复杂:
public class LocalDbContext : SqlServerDbContext public LocalDbContext()
: base("local")
{
//local 是连接字符串名字
} #region 父类抽象方法的实现 protected override bool CheckAllTableExists()
{
//创建用户表
CheckTableExists<UserEntity>();
return true;
} #endregion
}
综合结论:
所以SOD实体类对用户而言是透明的,它并没有增加使用的复杂性,又可以很好的控制数据量,还可以让你知道数据来自哪里,简单而又强大。
这样的ORM,才是合适DDD的ORM,当然,SOD不仅仅是一个ORM,它还有SQL-MAP和DataControl,具体可以看框架官网http://www.pwmis.com/sqlmap ,9年历史铸就的成果,坚固可靠。
附注:
下面是本文说明中使用的完整代码:
图片的效果要好些:
有关该测试程序的完整下载和查看,请看框架开源项目地址:
http://pwmis.codeplex.com/SourceControl/latest#SOD/Test/EntityTest-2013/Program.cs
其它:
SOD框架的数据容器,打造最适合DDD的ORM框架的更多相关文章
- DataSet的灵活,实体类的方便,DTO的效率:SOD框架的数据容器,打造最适合DDD的ORM框架
引言:DDD的困惑 最近,我看到园子里面有位朋友的一篇博客 <领域驱动设计系列(一):为何要领域驱动设计? >文章中有下面一段话,对DDD使用产生的疑问: •没有正确的使用ORM, 导致数 ...
- 代码片段添加智能提示,打造一款人见人爱的ORM框架
SqlSugar ORM优点: 1.高性能,达到原生最高水准,比SqlHelper性能要高,比Dapper快30% 比EF快50% 2.支持多种数据库 ,sql版本更新最快,其它会定期更新,可以在多种 ...
- 【ASP.NET程序员福利】打造一款人见人爱的ORM(一)
“很多人都不太认可以第三方ORM,因为考虑的点不够全面,没有大用户群体的ORM有保证,这点是不可否认确是事实.但是往往用户群体大的ORM又有不足之处,今天我们就来聊聊关于ORM的话题,打造 ...
- 自定义ORM框架(转转)
ORM背景 在数据库界,主流的数据库都是关系型数据库,其采用的关系型数据结构模型,无论从数学上还是实践中都相当的成熟,得到非常广泛的应用.在关系型数据结构理 论中,所有的数据都组织成一个个相互独立的二 ...
- .NET轻量级ORM框架Dapper入门精通
一.课程介绍 本次分享课程包含两个部分<.NET轻量级ORM框架Dapper修炼手册>和<.NET轻量级ORM框架Dapper葵花宝典>,阿笨将带领大家一起领略轻量级ORM框架 ...
- orm框架与缓存的关系
1.mybatis规定,一级缓存没必要bean类实现序列化,但二级缓存bean类必须实现序列化. 因为二级缓存是基于namespace的也就是基于接口的,二级缓存可以设置存储源,可以是redis或者m ...
- Orm框架开发之NewExpression合并问题
之前都是看别人写博客,自己没有写博客的习惯.在工作的过程中,总是会碰到许多的技术问题.有很多时候想记录下来,后面一直有许多的问题等着解决.总想着等系统完成了,再回头总结下.往往结果就把这事抛到脑后了. ...
- ORM框架SQLAlchemy学习
一.基本介绍 以下介绍来自维基百科,自由的百科全书. SQLAlchemy是Python编程语言下的一款开源软件.提供了SQL工具包及对象关系映射(ORM)工具,使用MIT许可证发行. SQLAlch ...
- Android开发数据库之第三方ORM框架(GreenDao)
移动APP追求追求功能实现的同一时候,用户体验很重要.開始APP的同一时候.要时刻的切换开发人员的角色,如你开发的时候.是 站在APP的开发角色,处于生产者的位置:当你測试的时候.你应该把自己放在用户 ...
随机推荐
- 房费制 它 结账BUG
声明:以下内容仅仅是对在桌子上的卡与卡表的后面,适合学生的表! 最近,我们已经开始做VB.NET系统重构版,在这里跟大家聊聊我在机房收费系统中发现的漏洞. 在机房收费系统中有这样一个窗口--结 ...
- android中怎么把自己须要的app启动图标集中到一个弹出框中
先看效果图 这个是我们自己的apk点击之后的效果 下边是布局文件 activity_main.xml主布局文件 <LinearLayout xmlns:android="http:// ...
- thinkphp3.2 代码生成并点击验证码
本人小菜鸟一仅仅.为了自我学习和交流PHP(jquery,linux,lamp,shell,javascript,server)等一系列的知识.小菜鸟创建了一个群.希望光临本博客的人能够进来交流.寻求 ...
- js 性能优化整理之 缓存变量
简单的常见的操作:假设每个便签添加一个 属性 -webkit-animation-delay:0.1s 递增操作::通过for循环添加 <ul id="uls"> ...
- Spring【AOP】
AOP是OOP的延续,是软件开发中的一个热点. AOP技术,是OOP补充. OOP引入封装.继承和多态建立一种对象层次结构模拟公共行为集合,而对从左到右的关系则显得无能为力.对于AOP则恰恰适应这样的 ...
- Java回合阵列List
package com.mine.practice.arrtolist; import java.util.ArrayList; import java.util.Arrays; import jav ...
- JQUERY省、市、县城市联动选择
JQUERY 插件开发——CITYLINKAGE(省.市.县城市联动选择) 第一部分:背景 开发源于需求,本次城市联动选择插件算是我写插件的一个特例吧,不是我目前工作需要些的,算是兴趣驱使吧.之前 ...
- SQLServer表变量对IO及内存影响测试
原文:SQLServer表变量对IO及内存影响测试 1. 测试创建表变量对IO的影响 测试创建表变量前后,tempdb的空间大小,目前使用sp_spaceused得到大小,也可以使用视图sys.dm_ ...
- atitit.提升稳定性---hibernate 添加重试retry 机制解决数据库连接关闭
atitit.提升稳定性---hibernate 添加重试retry 机制解决数据库连接关闭 1. 流程总结 retry(5times).invoke(xxx).test().rest().$() t ...
- Oracle按不同时间分组统计
Oracle按不同时间分组统计 Oracle按不同时间分组统计的sql 如下表table1: 日期(exportDate) 数量(amount) -------------- ----------- ...