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


    一:数据映射器

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

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

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

  • 相关阅读:
    python note 30 断点续传
    python note 29 线程创建
    python note 28 socketserver
    python note 27 粘包
    python note 26 socket
    python note 25 约束
    Sed 用法
    python note 24 反射
    python note 23 组合
    python note 22 面向对象成员
  • 原文地址:https://www.cnblogs.com/luminji/p/3734271.html
Copyright © 2020-2023  润新知