架构模式数据源模式之:数据映射器(Data Mapper)

简介: 一:数据映射器 关系型数据库用来存储数据和关系,对象则可以处理业务逻辑,所以,要把数据本身和业务逻辑糅杂到一个对象中,我们要么使用 活动记录,要么把两者分开,通过数据映射器把两者关联起来。 数据映射器是分离内存对象和数据库的中间软件层,下面这个时序图描述了这个中间软件层的概念: 在这个时序图中,我们还看到一个概念,映射器需能够获取领域对象(在这个例子中,a Person 就是一个领域对象)。

一:数据映射器

关系型数据库用来存储数据和关系,对象则可以处理业务逻辑,所以,要把数据本身和业务逻辑糅杂到一个对象中,我们要么使用 活动记录,要么把两者分开,通过数据映射器把两者关联起来。

数据映射器是分离内存对象和数据库的中间软件层,下面这个时序图描述了这个中间软件层的概念:

image

在这个时序图中,我们还看到一个概念,映射器需能够获取领域对象(在这个例子中,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;
            }
        }
    }
   

上面是一个简单的映射器,它具备了 标识映射 功能。由于有标识映射,所以我们运行这段代码得到的结果是:

image

回归本问实质,问题:什么叫 “数据映射”

其实,这个问题很关键,

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 的高效性。

Creative Commons License本文基于 Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 http://www.cnblogs.com/luminji(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。
目录
相关文章
|
5月前
|
SQL 关系型数据库 MySQL
Presto【基础 01】简介+架构+数据源+数据模型+特点(一篇即可入门支持到PB字节的分布式SQL查询引擎Presto)
Presto【基础 01】简介+架构+数据源+数据模型+特点(一篇即可入门支持到PB字节的分布式SQL查询引擎Presto)
61 0
|
12月前
|
存储 缓存 前端开发
Android 架构之 MVI 雏形 | 响应式编程 + 单向数据流 + 唯一可信数据源(下)
Android 架构之 MVI 雏形 | 响应式编程 + 单向数据流 + 唯一可信数据源
349 1
|
12月前
|
存储 前端开发 安全
Android 架构之 MVI 雏形 | 响应式编程 + 单向数据流 + 唯一可信数据源
Android 架构之 MVI 雏形 | 响应式编程 + 单向数据流 + 唯一可信数据源
167 0
|
Java Maven Spring
springboot高级功能(二)springboot核心架构,自定义start全解析(start中配置从数据源)
创建一个新的maven项目 新建Properties类(相当于调用的项目传入start的参数) 其中需要@ConfigurationProperties注解 prefix = &quot;login&quot; 的意思是调用的spring boot项目 application.properties中配置的参数 调用时传入的为
220 0
springboot高级功能(二)springboot核心架构,自定义start全解析(start中配置从数据源)
|
4天前
|
存储 监控 API
构建高效微服务架构:后端开发的现代实践
【5月更文挑战第9天】 在本文中,我们将深入探讨如何在后端开发中构建一个高效的微服务架构。通过分析不同的设计模式和最佳实践,我们将展示如何提升系统的可扩展性、弹性和维护性。我们还将讨论微服务架构在处理复杂业务逻辑和高并发场景下的优势。最后,我们将分享一些实用的工具和技术,以帮助开发者实现这一目标。
|
23小时前
|
负载均衡 JavaScript Java
构建高效微服务架构:后端开发的新视角
【5月更文挑战第13天】在现代软件开发中,微服务架构已经成为一种流行趋势。它通过将应用程序拆分为一组小型、独立的服务来提高可扩展性、弹性和可维护性。本文将探讨如何构建一个高效的微服务架构,包括选择合适的技术栈、设计良好的服务接口、确保数据一致性以及实现有效的服务发现和负载均衡。
|
23小时前
|
监控 Java 开发者
构建高效微服务架构:后端开发的新趋势
【5月更文挑战第13天】随着现代应用的复杂性日益增加,传统的单体应用架构已不足以满足快速迭代和可扩展性的需求。本文将探讨如何通过微服务架构来提升后端开发的效率和系统的可靠性,涵盖微服务设计原则、技术栈选择、部署策略以及维护实践。我们将分析微服务的优势与挑战,并提供一系列实施建议,帮助开发者在构建和维护分布式系统时做出明智决策。
|
1天前
|
监控 持续交付 数据库
构建高效可靠的微服务架构:后端开发的新范式
【5月更文挑战第13天】 在当今软件开发的世界中,微服务架构已经成为了一种流行且有效的设计模式。它通过将大型复杂系统分解为一组独立的、可部署的服务来提高系统的可维护性、可扩展性和敏捷性。本文将探讨如何构建一个高效且可靠的微服务架构,包括关键的设计原则、技术选型以及可能面临的挑战。我们的目标是为后端开发者提供一套实用的指南,以便在构建现代化应用程序时做出明智的决策。
|
2天前
|
监控 API 开发者
构建高效微服务架构:后端开发的新范式
【5月更文挑战第12天】 在现代软件开发的浪潮中,微服务架构已经成为了设计复杂系统的首选模式。它通过将大型应用程序拆分成一组小而专注的服务来增强系统的可维护性和可扩展性。本文将探讨微服务架构的关键概念、优势以及如何在后端开发中实现一个高效的微服务系统。我们还将讨论一些常见的挑战和最佳实践,以帮助开发者避免陷入常见的陷阱。
15 6
|
2天前
|
存储 NoSQL MongoDB
【MongoDB 专栏】MongoDB 与微服务架构的结合
【5月更文挑战第11天】微服务架构流行趋势下,选择合适的数据库至关重要。MongoDB作为非关系型数据库,与微服务有天然契合度。其灵活的文档模型、水平扩展性、高性能及局部事务支持,满足微服务对数据模型多样性、高可用性、快速读写的需求。实践中,需注意数据划分、索引优化、监控调优和版本控制。未来,MongoDB在微服务中的应用将更广泛,新技术将提升其在微服务架构中的价值。
【MongoDB 专栏】MongoDB 与微服务架构的结合