架构模式数据源模式之:数据映射器(Data Mapper)
一:数据映射器
关系型数据库用来存储数据和关系,对象则可以处理业务逻辑,所以,要把数据本身和业务逻辑糅杂到一个对象中,我们要么使用 活动记录,要么把两者分开,通过数据映射器把两者关联起来。
数据映射器是分离内存对象和数据库的中间软件层,下面这个时序图描述了这个中间软件层的概念:
在这个时序图中,我们还看到一个概念,映射器需能够获取领域对象(在这个例子中,a Person 就是一个领域对象)。而对于数据的变化(或者说领域对象的变化),映射器还必须要知道这些变化,在这个时候,我们就需要 工作单元 模式(后议)。
从上图中,我们仿佛看到 数据映射器 还蛮简单的,复杂的部分是:我们需要处理联表查询,领域对象的继承等。领域对象的字段则可能来自于数据库中的多个表,这种时候,我们就必须要让数据映射器做更多的事情。是的,以上我们说到了,数据映射器要能做到两个复杂的部分:
1:感知变化;
2:通过联表查询的结果,为领域对象赋值;
为了感知变化以及与数据库对象保持一致,则需要 标识映射(架构模式对象与关系结构模式之:标识域(Identity Field)),这通常需要有 标识映射的注册表,或者为每个查找方法持有一个 标识映射,下面的代码是后者:
void Main()
{
SqlHelper.ConnectionString = "Data Source=xxx;Initial Catalog=xxx;Integrated Security=False;User ID=sa;Password=xxx;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False";
var user1 = User.FindUser("6f7ff44435f3412cada61898bcf0df6c");
var user2 = User.FindUser("6f7ff44435f3412cada61898bcf0df6c");
(user1 == user2).Dump();
"END".Dump();
}public abstract class BaseMode
{
public string Id {get; set;}public string Name {get; set;}
}public class User : BaseMode
{
static UserMap map = new UserMap();
public static User FindUser(string id)
{
var user = map.Find(id);
return user;
}
}public class UserMap : AbstractMapper<User>
{
public User Find(string id)
{
return (User)AbstractFind(id);
}
protected override User AbstractFind(string id)
{
var user = base.AbstractFind(id);
if( user == null )
{
"is Null".Dump();
string sql = "SELECT * FROM [EL_Organization].[User] WHERE ID=@Id";
var pms = new SqlParameter[]
{
new SqlParameter("@Id", id)
};
var ds = SqlHelper.ExecuteDataset(CommandType.Text, sql, pms);
user = DataTableHelper.ToList<User>(ds.Tables[0]).FirstOrDefault();
if(user == null)
{
return null;
}
user = Load(user);
return user;
}
return user;
}
public List<User> FindList(string name)
{
// SELECT * FROM USER WHERE NAME LIKE NAME
List<User> users = null;
return LoadAll(users);
}
public void Update(User user)
{
// UPDATE USER SET ....
}
}
public abstract class AbstractMapper<T> where T : BaseMode
{
// 这里的问题是,随着对象消失,loadedMap就被回收
protected Dictionary<string, T> loadedMap = new Dictionary<string, T>();
protected T Load(T t)
{
if(loadedMap.ContainsKey(t.Id) )
{
return loadedMap[t.Id];
}
else
{
loadedMap.Add(t.Id, t);
return t;
}
}
protected List<T> LoadAll(List<T> ts)
{
for(int i=0; i < ts.Count; i++)
{
ts[i] = Load(ts[i]);
}
return ts;
}
protected virtual T AbstractFind(string id)
{
if(loadedMap.ContainsKey(id))
{
return loadedMap[id];
}
else
{
return null;
}
}
}
上面是一个简单的映射器,它具备了 标识映射 功能。由于有标识映射,所以我们运行这段代码得到的结果是:
回归本问实质,问题:什么叫 “数据映射”
其实,这个问题很关键,
UserMap 通过 Find 方法,将数据库记录变成了一个 User 对象,这就叫 “数据映射”,但是,真正起到核心作用的是 user = DataTableHelper.ToList<User>(ds.Tables[0]).FirstOrDefault(); 这行代码。更进一步的,DataTableHelper.ToList<T> 这个方法完成了 数据映射 功能。
那么,DataTableHelper.ToList<T> 方法具体干了什么事情,实际上,无非就是根据属性名去获取 DataTable 的字段值。这是一种简便的方法,或者说,在很多业务不复杂的场景下,这也许是个好办法,但是,因为业务往往是复杂的,所以实际情况下,我们使用这个方法的情况并不是很多,大多数情况下,我们需要像这样编码来完成映射:
someone.Name = Convert.ToString(row["Name"])
不要怀疑,上面这行代码,就叫数据映射,任何高大上的概念,实际上就是那条你写了很多遍的代码。
1.1 EntityFramework 中的数据映射
这是一个典型的 EF 的数据映射类,
public class CourseMap : EntityTypeConfiguration<Course>
{
public CourseMap()
{
// Primary Key
this.HasKey(t => t.CourseID);// Properties
this.Property(t => t.CourseID)
.HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);
this.Property(t => t.Title)
.IsRequired()
.HasMaxLength(100);
// Table & Column Mappings
this.ToTable("Course");
this.Property(t => t.CourseID).HasColumnName("CourseID");
this.Property(t => t.Title).HasColumnName("Title");
this.Property(t => t.Credits).HasColumnName("Credits");
this.Property(t => t.DepartmentID).HasColumnName("DepartmentID");// Relationships
this.HasMany(t => t.People)
.WithMany(t => t.Courses)
.Map(m =>
{
m.ToTable("CourseInstructor");
m.MapLeftKey("CourseID");
m.MapRightKey("PersonID");
});
this.HasRequired(t => t.Department)
.WithMany(t => t.Courses)
.HasForeignKey(d => d.DepartmentID);
}
}
我们可以看到,EF 的数据映射,那算是真正的数据映射。最基本的,其在内部无非是干了一件这样的事情:
数据库是哪个字段,对应的内存对象的属性是哪个属性。
最终,它都是通过一个对象工厂把领域模型生成出来,其原理大致如下:
internal static Course BuildCourse(IDataReader reader)
{
Course course = new Course(reader[FieldNames.CourseId]);
contract.Title = reader[FieldNames.Title].ToString();
…
return contract;
}
二:仓储库
UserMap 关于 数据映射器 的概念是不是觉得太重了?因为它干了 映射 和 持久化 的事情,它甚至还得持有 工作单元。那么,如果我们能不能像 EF 一样,映射器 只干映射的事情,而把其余事情分出去呢?可以,分离出去的这部分就叫做 仓储库。
三:再多说一点 DataTableHelper.ToList<T>,简化的数据映射器
其实就是 DataTable To List 了。如果你在用 EF 或者 NHibernate 这样的框架,那么,就用它们提供的映射器好了(严格来说,你不是在使用它们的映射器。因为这些框架本身才是在使用自己的映射器,我们只是在配置映射器所要的数据和关系而已,有时候,这些配置是在配置文件中,有时候是在字段或属性上加 Attribute,有时候则是简单但庞大的单行代码)。我们当然也可以创建自己的 标准的 映射器,Tim McCarthy 在 《领域驱动设计 C# 2008 实现》 中就实现了这样的映射器。但是,EF 和 NHibernate 固然很好,但是很多时候我们还是不得不使用 手写SQL,因为:
1:EF 和 NHibernate 是需要学习成本的,这代表者团队培训成本高,且易出错的;
2:不应放弃 手写SQL 的高效性。
架构模式数据源模式之:数据映射器(Data Mapper)的更多相关文章
- 架构模式数据源模式之:表数据入口(Table Data Gateway)、行数据入口(Row Data Gateway)、活动记录(Active Record)
一:表数据入口(Table Data Gateway) 表数据入口提供了用于访问单个表或者视图(也包含了联表查询)的所有SQL,通常一个表一个类.其它代码通过它来实现对数据库的交互.基于这个特点,表数 ...
- 架构模式中的Active Record和Data Mapper
架构模式中的Active Record和Data Mapper 概念 在简单应用中,领域模型是一种和数据库结构一致的简单结构,对应每个数据库表都有一个领域类,在这种情况下,有必要让每个对象负责数据库的 ...
- 《深入浅出MyBatis技术原理与实战》——4. 映射器,5. 动态SQL
4.1 映射器的主要元素 4.2 select元素 4.2.2 简易数据类型的例子 例如,我们需要统计一个姓氏的用户数量.应该把姓氏作为参数传递,而将结果设置为整型返回给调用者,如: 4.2.3 自动 ...
- 关于QtCharts中的映射器与模型的使用
简述 本文章基于博主在使用QtCharts中一些经验总结,相关了Qt类有QVXYModelMapper,CustomTableModel(一个继承了QAbstractTableModel的类,用于实现 ...
- 第六章 mybatis注入映射器
为了代替手工使用 SqlSessionDaoSupport 或 SqlSessionTemplate 编写数据访问对象 (DAO)的代码,MyBatis-Spring 提供了一个动态代理的实现:Map ...
- java-mybaits-010-mybatis-spring-使用 SqlSession、注入映射器
一. SqlSession概述 在 MyBatis 中,你可以使用 SqlSessionFactory 来创建 SqlSession.一旦你获得一个 session 之后,你可以使用它来执行映射语句, ...
- MyBatis-Spring 学习笔记一 SqlSessionFactoryBean以及映射器类
MyBatis-Spring 是一个用来整合 MyBatis 和 Spring 框架的小类库,通过这个jar包可以将 MyBatis 代码地整合到 Spring 中. 使用这个类库中的类, Sprin ...
- MyBatis映射器(转载)
什么是MyBatis映射器? MyBatis框架包括两种类型的XML文件,一类是配置文件,即mybatis-config.xml,另外一类是映射文件,例如XXXMapper.xml等.在MyBatis ...
- iBatis.Net(C#)SQL数据映射
转载请注明 http://www.cnblogs.com/13590/archive/2013/03/01/2938126.html 摘要:本文探讨了iBatis.Net框架的XML数据映射文件各配置 ...
随机推荐
- springboot mybatis 分页整合
spring boot 整合mybatis ,分两块mybatis 整合,分页整合. 1.pom文件增加 <dependency> <groupId>org.mybatis ...
- 三个UID
1.三个UID 这三个UID分别是实际用户ID(real uid).有效用户ID(effective uid).保存的设置用户ID(saved set-user-ID)(SUID) 实际用户ID(RU ...
- web项目文件夹上传
最近公司做工程项目,实现文件夹上传 网上找了很久,发现网上很多代码大都存在很多问题,不过还是让我找到了一个符合要求的项目. 对项目的文件夹上传功能做出分析,找出文件夹上传的原理,对文件夹的传输模式深入 ...
- 几种修改Linux主机名的方法
在安装一些系统时,需要修改hostname,比如安装Hadoop时需要修改主机名,而且主机名不能包含下划线. 实际上,主机名分三种(命令hostnamectl或hostnamectl status可查 ...
- ID、句柄、指针、对象互相转换
/*************************************************************************************************** ...
- android-基础编程-ListView
ListView主要包括view和数据源.其数据适配器列表分为三种,ArrayAdapter,SimpleAdapter和SimpleCursorAdapter. ListView的没有oom原因.经 ...
- java基础-day13
第01天 java面向对象 今日内容介绍 u 继承 u 抽象类 第1章 继承 1.1 继承的概述 在现实生活中,继承一般指的是子女继承父辈的财产.在程序中,继承描述的是事物之间的所属关系,通过继 ...
- Vue的配置
一.build:打包的配置文件的文件夹 1.build.js 生产版本的配置文件,一般这个文件我们是不改的 'use strict' //调用检查版本的文件,check-versions的导出直接是 ...
- [leet code 198]House Robber
1 题目 You are a professional robber planning to rob houses along a street. Each house has a certain a ...
- 实验1 单片机IO口应用及数码管显示
1. 单片机驱动蜂鸣器的实验: a) 说明:Lab51单片机实验板的蜂鸣器连接到单片机的P1.5 b) 基本要求:控制蜂鸣器每2秒响0.5秒. #include &l ...