天天看點

架構模式資料源模式之:資料映射器(Data Mapper)

一:資料映射器

關系型資料庫用來存儲資料和關系,對象則可以處理業務邏輯,是以,要把資料本身和業務邏輯糅雜到一個對象中,我們要麼使用 活動記錄,要麼把兩者分開,通過資料映射器把兩者關聯起來。

資料映射器是分離記憶體對象和資料庫的中間軟體層,下面這個時序圖描述了這個中間軟體層的概念:

架構模式資料源模式之:資料映射器(Data Mapper)

在這個時序圖中,我們還看到一個概念,映射器需能夠擷取領域對象(在這個例子中,a Person 就是一個領域對象)。而對于資料的變化(或者說領域對象的變化),映射器還必須要知道這些變化,在這個時候,我們就需要 工作單元 模式(後議)。

從上圖中,我們仿佛看到 資料映射器 還蠻簡單的,複雜的部分是:我們需要處理聯表查詢,領域對象的繼承等。領域對象的字段則可能來自于資料庫中的多個表,這種時候,我們就必須要讓資料映射器做更多的事情。是的,以上我們說到了,資料映射器要能做到兩個複雜的部分:

1:感覺變化;

2:通過聯表查詢的結果,為領域對象指派;

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;              }          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];                  return null; 

上面是一個簡單的映射器,它具備了 辨別映射 功能。由于有辨別映射,是以我們運作這段代碼得到的結果是:

架構模式資料源模式之:資料映射器(Data Mapper)

回歸本問實質,問題:什麼叫 “資料映射”

其實,這個問題很關鍵,

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

本文轉自最課程陸敏技部落格園部落格,原文連結:http://www.cnblogs.com/luminji/p/3734271.html,如需轉載請自行聯系原作者