当前位置:首页 > PHP教程 > php高级应用 > 列表

数据源架构模式 表入口 行入口 活动记录 数据映射器

发布:smiling 来源: PHP粉丝网  添加日期:2018-10-28 20:04:06 浏览: 评论:0 

数据源架构模式 - 表入口模式

表入口模式充当数据库表访问入口的对象,一个实例处理表中的所有行。

可以理解为对之前分散在各个页面的sql语句进行封装,一张表就是一个对象,该对象处理所有与该表有关的业务逻辑,很好的提高了代码的复用性。

现在想起来,当初刚毕业那会儿,经常使用表入口模式。

具体的实现方式参见代码:

database.php

  1. <?php    
  2. class Database{   
  3.     //只是为了演示,通常情况下数据库的配置是会单独写在配置文件中的   
  4.     private static $_dbConfig = array(   
  5.         'host' => '127.0.0.1',   
  6.         'username' => 'root',   
  7.         'pwd' => '',   
  8.         'dbname' => 'bussiness'   
  9.         );   
  10.     
  11.     private static $_instance;   
  12.     
  13.     public static function getInstance(){   
  14.         if(is_null(self::$_instance)){   
  15.             self::$_instance = new mysqli(self::$_dbConfig['host'], self::$_dbConfig['username'], self::$_dbConfig['pwd'], self::$_dbConfig['dbname']);    
  16.             if(self::$_instance->connect_errno){   
  17.                 throw new Exception(self::$_instance->connect_error);   
  18.             }   
  19.         }   
  20.         return self::$_instance;   
  21.     }   
  22.     
  23.     

person.php

  1. <?php    
  2. require_once 'database.php';   
  3. class Person extends Database{   
  4.     
  5.     public $instance;   
  6.     
  7.     public $table = 'person';   
  8.     
  9.     public function __construct(){   
  10.         $this->instance = Person::getInstance();   
  11.     }   
  12.     
  13.     public function getPersonById($personId){   
  14.         $sql = "<a href="\"/tags.php/select/\"" target="\"_blank\"">select</a> * from $this->table where id=$personId";   
  15.         echo $sql;   
  16.         return $this->instance->query($sql);   
  17.     }   
  18.     
  19.     /**其他的一些增删改查操作方法...**/   

index.php

  1. require_once 'person.php';   
  2.     
  3. $person = new Person();   
  4. var_dump($person->getPersonById(1)->fetch_assoc());   
  5. die(); 

运行结果:

  1. select * from person where id=1   
  2. array (size=2)   
  3.   'id' => string '1' (length=1)   
  4.   'name' => string 'ben' (length=3)   

数据源架构模式 - 行入口模式

一、概念

行数据入口(Row Data Gateway):充当数据源中单条记录入口的对象,每行一个实例。

二、简单实现行数据入口

为了方便理解,还是先简单实现:

  1. <?php 
  2.    
  3. /** 
  4.  * 企业应用架构 数据源架构模式之行数据入口 2010-09-27 sz 
  5.  * @author phppan.p#gmail.com  http://www.phppan.com 
  6.  * 哥学社成员(http://www.blog-brother.com/) 
  7.  * @package architecture 
  8.  */ 
  9. class PersonGateway { 
  10.    
  11.     private $_name
  12.     private $_id
  13.     private $_birthday
  14.    
  15.     public function __construct($id$name$birthday) { 
  16.         $this->setId($id); 
  17.         $this->setName($name); 
  18.         $this->setBirthday($birthday); 
  19.     } 
  20.    
  21.     public function getName() { 
  22.         return $this->_name; 
  23.     } 
  24.    
  25.     public function setName($name) { 
  26.         $this->_name = $name
  27.     } 
  28.    
  29.     public function getId() { 
  30.         return $this->_id; 
  31.     } 
  32.    
  33.     public function setId($id) { 
  34.         $this->_id = $id
  35.     } 
  36.    
  37.     public function getBirthday() { 
  38.         return $this->_birthday; 
  39.     } 
  40.    
  41.     public function setBirthday($birthday) { 
  42.         $this->_birthday = $birthday
  43.     } 
  44.    
  45.     /** 
  46.      * 入口类自身拥有更新操作 
  47.      */ 
  48.     public function update() { 
  49.         $data = array('id' => $this->_id, 'name' => $this->_name, 'birthday' => $this->_birthday); 
  50.    
  51.         $sql = "UPDATE person SET "
  52.         <a href="\"/tags.php/foreach/\"" target="\"_blank\"">foreach</a> ($data as $field => $value) { 
  53.             $sql .= "`" . $field . "` = '" . $value . "',"
  54.         } 
  55.         $sql = <a href="\"/tags.php/substr/\"" target="\"_blank\"">substr</a>($sql, 0, -1); 
  56.    
  57.         $sql .= " WHERE id = " . $this->_id; 
  58.    
  59.         return DB::query($sql); 
  60.     } 
  61.    
  62.     /** 
  63.      * 入口类自身拥有插入操作 
  64.      */ 
  65.     public function insert() { 
  66.         $data = array('name' => $this->_name, 'birthday' => $this->_birthday); 
  67.    
  68.         $sql = "INSERT INTO person "
  69.         $sql .= "(`" . implode("`,`"array_keys($data)) . "`)"
  70.         $sql .= " VALUES('" . implode("','"array_values($data)) . "')"
  71.    
  72.         return DB::query($sql); 
  73.     } 
  74.    
  75.     public static function load($rs) { 
  76.         /* 此处可加上缓存 */ 
  77.         return new PersonGateway($rs['id'] ? $rs['id'] : NULL, $rs['name'], $rs['birthday']); 
  78.     } 
  79.    
  80.    
  81. /** 
  82.  * 人员查找类 
  83.  */ 
  84. class PersonFinder { 
  85.    
  86.     public function find($id) { 
  87.         $sql = "SELECT * FROM person WHERE id = " . $id
  88.         $rs = DB::query($sql); 
  89.    
  90.         return PersonGateway::load($rs); 
  91.     } 
  92.    
  93.     public function findAll() { 
  94.         $sql = "SELECT * FROM person"
  95.         $rs = DB::query($sql); 
  96.    
  97.         $result = array(); 
  98.         if (is_array($rs)) { 
  99.             foreach ($rs as $row) { 
  100.                 $result[] = PersonGateway::load($row); 
  101.             } 
  102.         } 
  103.    
  104.         return $result
  105.     } 
  106.    
  107.    
  108. class DB { 
  109.    
  110.     /** 
  111.      * 这只是一个执行SQL的演示方法 
  112.      * @param string $sql   需要执行的SQL 
  113.      */ 
  114.     public static function query($sql) { 
  115.         echo "执行SQL: "$sql" <br />"
  116.    
  117.         if (strpos($sql'SELECT') !== FALSE) { //  示例,对于select查询返回查询结果 
  118.             return array('id' => 1, 'name' => 'Martin''birthday' => '2010-09-15'); 
  119.         } 
  120.     } 
  121.    
  122.    
  123. /** 
  124.  * 客户端调用 
  125.  */ 
  126. class Client { 
  127.    
  128.     /** 
  129.      * Main program. 
  130.      */ 
  131.     public static function main() { 
  132.    
  133.    
  134.         header("Content-type:text/html; charset=utf-8"); 
  135.    
  136.         /* 写入示例 */ 
  137.         $data = array('name' => 'Martin''birthday' => '2010-09-15'); 
  138.         $person = PersonGateway::load($data); 
  139.         $person->insert(); 
  140.    
  141.         /* 更新示例 */ 
  142.         $data = array('id' => 1, 'name' => 'Martin''birthday' => '2010-09-15'); 
  143.         $person = PersonGateway::load($data); 
  144.         $person->setName('Phppan'); 
  145.         $person->update(); 
  146.    
  147.         /* 查询示例 */ 
  148.         $finder = new PersonFinder(); 
  149.         $person = $finder->find(1); 
  150.         echo $person->getName(); 
  151.   //phpfensi.com 
  152.     } 
  153.    
  154.    
  155. Client::main(); 
  156. ?> 

三、运行机制

●行数据入口是单条记录极其相似的对象,在该对象中数据库中的每一列为一个域。

●行数据入口一般能实现从数据源类型到内存中类型的任意转换。

●行数据入口不存在任何领域逻辑,如果存在,则是活动记录。

●在实例可看到,为了从数据库中读取信息,设置独立的OrderFinder类。当然这里也可以选择不新建类,采用静态查找方法,但是它不支持需要为不同数据源提供不同查找方法的多态。因此这里最好单独设置查找方法的对象。

●行数据入口除了可以用于表外还可以用于视图。需要注意的是视图的更新操作。

●在代码中可见“定义元数据映射”,这是一种很好的作法,这样一来,所有的数据库访问代码都可以在自动建立过程中自动生成。

四、使用场景

4.1 事务脚本

可以很好地分离数据库访问代码,并且也很容易被不同的事务脚本重用。不过可能会发现业务逻辑在多处脚本中重复出现,这些逻辑可能在行数据入口中有用。不断移动这些逻辑会使行数据入口演变为活动记录,这样减少了业务逻辑的重复。

4.2 领域模型

如果要改变数据库的结构但不想改变领域逻辑,采用行数据入口是不错的选择。大多数情况,数据映射器更加适合领域模型。

行数据入口能和数据映射器一起配合使用,尽管这样看起来有点多此一举,不过,当行数据入口从元数据自动生成,而数据映射器由手动实现时,这种方法会很有效。

数据源架构模式 - 活动记录

【活动记录的意图】

一个对象,它包装数据表或视图中某一行,封装数据库访问,并在这些数据上增加了领域逻辑。

【活动记录的适用场景】

适用于不太复杂的领域逻辑,如CRUD操作等。

【活动记录的运行机制】

对象既有数据又有行为。其使用最直接的方法,将数据访问逻辑置于领域对象中。

活动记录的本质是一个领域模型,这个领域模型中的类和基数据库中的记录结构应该完全匹配,类的每个域对应表的每一列。

一般来说,活动记录包括如下一些方法:

1、由数据行构造一个活动记录实例;

2、为将来对表的插入构造一个新的实例;

3、用静态查找方法来包装常用的SQL查询和返回活动记录;

4、更新数据库并将活动记录中的数据插入数据库;

5、获取或设置域;

6、实现部分业务逻辑。

【活动记录的优点和缺点】

优点:

1、简单,容易创建并且容易理解。

2、在使用事务脚本时,减少代码复制。

3、可以在改变数据库结构时不改变领域逻辑。

4、基于单个活动记录的派生和测试验证会很有效。

缺点:

1、没有隐藏关系数据库的存在。

2、仅当活动记录对象和数据库中表直接对应时,活动记录才会有效。

3、要求对象的设计和数据库的设计紧耦合,这使得项目中的进一步重构很困难

【活动记录与其它模式】

数据源架构模式之行数据入口:活动记录与行数据入口十分类似。二者的主要差别是行数据入口 仅有数据库访问而活动记录既有数据源逻辑又有领域逻辑。

【活动记录的PHP示例】

  1. <?php   
  2. /**   
  3. * 企业应用架构 数据源架构模式之活动记录 2010-10-17 sz   
  4. * @author phppan.p#gmail.com  http://www.phppan.com   
  5. * 哥学社成员(http://www.blog-brother.com/)   
  6. * @package architecture   
  7. */  
  8. /**   
  9. * 定单类   
  10. */  
  11. class Order {   
  12. /**   
  13. *  定单ID   
  14. * @var <type>   
  15. */  
  16. private $_order_id;   
  17. /**   
  18. * 客户ID   
  19. * @var <type>   
  20. */  
  21. private $_customer_id;   
  22. /**   
  23. * 定单金额   
  24. * @var <type>   
  25. */  
  26. private $_amount;   
  27. public function __construct($order_id$customer_id$amount) {   
  28. $this->_order_id = $order_id;   
  29. $this->_customer_id = $customer_id;   
  30. $this->_amount = $amount;   
  31. }   
  32. /**   
  33. * 实例的删除操作   
  34. */  
  35. public function delete() {   
  36. $sql = "DELETE FROM Order SET WHERE order_id = " . $this->_order_id . " AND customer_id = "  . $this->_customer_id;   
  37. return DB::query($sql);   
  38. }   
  39. /**   
  40. * 实例的更新操作   
  41. */  
  42. public function update() {   
  43. }   
  44. /**   
  45. * 插入操作   
  46. */  
  47. public function insert() {   
  48. }   
  49. public static function load($rs) {   
  50. return new Order($rs['order_id'] ? $rs['order_id'] : NULL, $rs['customer_id'], $rs['amount'] ? $rs['amount'] : 0);   
  51. }   
  52. }   
  53. class Customer {   
  54. private $_name;   
  55. private $_customer_id;   
  56. public function __construct($customer_id$name) {   
  57. $this->_customer_id = $customer_id;   
  58. $this->_name = $name;   
  59. }   
  60. /**   
  61. * 用户删除定单操作 此实例方法包含了业务逻辑   
  62. * 通过调用定单实例实现   
  63. * 假设此处是对应的删除操作(实际中可能是一种以某字段来标记的假删除操作)   
  64. */  
  65. public function deleteOrder($order_id) {   
  66. $order = Order::load(array('order_id' => $order_id'customer_id' => $this->_customer_id));   
  67. return $order->delete();   
  68. }   
  69. /**   
  70. * 实例的更新操作   
  71. */  
  72. public function update() {   
  73. }   
  74. /**   
  75. * 入口类自身拥有插入操作   
  76. */  
  77. public function insert() {   
  78. }   
  79. public static function load($rs) {   
  80. /* 此处可加上缓存 */  
  81. return new Customer($rs['customer_id'] ? $rs['customer_id'] : NULL, $rs['name']);   
  82. }   
  83. /**   
  84. * 根据客户ID 查找   
  85. * @param integer $id   客户ID   
  86. * @return  Customer 客户对象   
  87. */  
  88. public static function find($id) {   
  89. return CustomerFinder::find($id);   
  90. }   
  91. }   
  92. /**   
  93. * 人员查找类   
  94. */  
  95. class CustomerFinder {   
  96. public static function find($id) {   
  97. $sql = "SELECT * FROM person WHERE customer_id = " . $id;   
  98. $rs = DB::query($sql);   
  99. return Customer::load($rs);   
  100. }   
  101. }   
  102. class DB {   
  103. /**   
  104. * 这只是一个执行SQL的演示方法   
  105. * @param string $sql   需要执行的SQL   
  106. */  
  107. public static function query($sql) {   
  108. echo "执行SQL: "$sql" <br />";   
  109. if (strpos($sql'SELECT') !== FALSE) { //  示例,对于select查询返回查询结果   
  110. return array('customer_id' => 1, 'name' => 'Martin');   
  111. }   
  112. }   
  113. }   
  114. /**   
  115. * 客户端调用   
  116. */  
  117. class Client {   
  118. /**   
  119. * Main program.   
  120. */  
  121. public static function main() {   
  122. header("Content-type:text/html; charset=utf-8");   
  123. /* 加载客户ID为1的客户信息 */  
  124. $customer = Customer::find(1);   
  125. /* 假设用户拥有的定单id为 9527*/  
  126. $customer->deleteOrder(9527);   
  127. }  //phpfensi.com 
  128. }   
  129. Client::main();   
  130. ?> 

同前面的文章一样,这仅仅是一个活动记录的示例,关于活动记录模式的应用,可以查看Yii框架中的DB类,在其源码中有一个CActiveRecord抽象类,从这里可以看到活动记录模式的应用

另外,如果从事务脚本中创建活动记录,一般是首先将表包装为入口,接着开始行为迁移,使表深化成为活动记录。

对于活动记录中的域的访问和设置可以如yii框架一样,使用魔术方法__set方法和__get方法。

数据源架构模式 - 数据映射器

一:数据映射器

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

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

数据源架构模式 表入口 行入口 活动记录 数据映射器

在这个时序图中,我们还看到一个概念,映射器需能够获取领域对象(在这个例子中,a Person 就是一个领域对象)。而对于数据的变化(或者说领域对象的变化),映射器还必须要知道这些变化,在这个时候,我们就需要 工作单元 模式(后议)。

从上图中,我们仿佛看到 数据映射器 还蛮简单的,复杂的部分是:我们需要处理联表查询,领域对象的继承等。领域对象的字段则可能来自于数据库中的多个表,这种时候,我们就必须要让数据映射器做更多的事情。是的,以上我们说到了,数据映射器要能做到两个复杂的部分:

1:感知变化;

2:通过联表查询的结果,为领域对象赋值;

为了感知变化以及与数据库对象保持一致,则需要 标识映射(架构模式对象与关系结构模式之:标识域(Identity Field)),这通常需要有 标识映射的注册表,或者为每个查找方法持有一个 标识映射,下面的代码是后者:

  1. void Main() 
  2.     SqlHelper.ConnectionString = "Data Source=xxx;Initial Catalog=xxx;Integrated Security=False;User ID=sa;Password=xxx;Connect Timeout=15;Encrypt=False;TrustServerCertificate=False"
  3.     var user1 = User.FindUser("6f7ff44435f3412cada61898bcf0df6c"); 
  4.     var user2 = User.FindUser("6f7ff44435f3412cada61898bcf0df6c"); 
  5.     (user1 == user2).Dump(); 
  6.     "END".Dump(); 
  7.  
  8.     public abstract class BaseMode 
  9.     { 
  10.         public string Id {get; set;} 
  11.  
  12.         public string Name {get; set;} 
  13.     } 
  14.  
  15.     public class User : BaseMode 
  16.     { 
  17.         static UserMap map = new UserMap(); 
  18.         public static User FindUser(string id) 
  19.         { 
  20.             var user = map.Find(id); 
  21.             return user; 
  22.         } 
  23.     } 
  24.  
  25.     public class UserMap : AbstractMapper<User> 
  26.     { 
  27.         public User Find(string id) 
  28.         { 
  29.             return (User)AbstractFind(id); 
  30.         } 
  31.         
  32.         protected override User AbstractFind(string id) 
  33.         { 
  34.             var user = base.AbstractFind(id); 
  35.             if( user == null ) 
  36.             { 
  37.                 "is Null".Dump(); 
  38.                 string sql = "SELECT * FROM [EL_Organization].[User] WHERE ID=@Id"
  39.                 var pms = new SqlParameter[] 
  40.                 { 
  41.                     new SqlParameter("@Id", id) 
  42.                 }; 
  43.                 
  44.                 var ds = SqlHelper.ExecuteDataset(CommandType.Text, sql, pms); 
  45.                 user = DataTableHelper.ToList<User>(ds.Tables[0]).FirstOrDefault(); 
  46.                 if(user == null) 
  47.                 { 
  48.                     return null; 
  49.                 } 
  50.                 
  51.                 user = Load(user); 
  52.                 return user; 
  53.             } 
  54.             
  55.             return user; 
  56.         } 
  57.         
  58.         public List<User> FindList(string name) 
  59.         { 
  60.             // SELECT * FROM USER WHERE NAME LIKE NAME 
  61.             List<User> users = null; 
  62.             return LoadAll(users); 
  63.         } 
  64.         
  65.         public void Update(User user) 
  66.         { 
  67.             // UPDATE USER SET .... 
  68.         } 
  69.     } 
  70.     
  71.     public abstract class AbstractMapper<T> where T : BaseMode 
  72.     { 
  73.         // 这里的问题是,随着对象消失,loadedMap就被回收 
  74.         protected Dictionary<string, T> loadedMap = new Dictionary<string, T>(); 
  75.         
  76.         protected T Load(T t) 
  77.         { 
  78.             if(loadedMap.ContainsKey(t.Id) ) 
  79.             { 
  80.                 return loadedMap[t.Id]; 
  81.             } 
  82.             else 
  83.             { 
  84.                 loadedMap.Add(t.Id, t); 
  85.                 return t; 
  86.             } 
  87.         } 
  88.         
  89.         protected List<T> LoadAll(List<T> ts) 
  90.         { 
  91.             for(int i=0; i < ts.Count; i++) 
  92.             { 
  93.                 ts[i] = Load(ts[i]); 
  94.             } 
  95.             
  96.             return ts; 
  97.         } 
  98.         
  99.         protected virtual T AbstractFind(string id) 
  100.         { 
  101.             if(loadedMap.ContainsKey(id)) 
  102.             { 
  103.                 return loadedMap[id]; 
  104.             } //phpfensi.com 
  105.             else 
  106.             { 
  107.                 return null; 
  108.             } 
  109.         } 
  110.     } 

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

数据源架构模式 表入口 行入口 活动记录 数据映射器

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

其实,这个问题很关键,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 的数据映射类,

  1. public class CourseMap : EntityTypeConfiguration<Course> 
  2.     public CourseMap() 
  3.     { 
  4.         // Primary Key 
  5.         this.HasKey(t => t.CourseID); 
  6.  
  7.         // Properties 
  8.         this.Property(t => t.CourseID) 
  9.             .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None); 
  10.         this.Property(t => t.Title) 
  11.             .IsRequired() 
  12.             .HasMaxLength(100); 
  13.         // Table & Column Mappings 
  14.         this.ToTable("Course"); 
  15.         this.Property(t => t.CourseID).HasColumnName("CourseID"); 
  16.         this.Property(t => t.Title).HasColumnName("Title"); 
  17.         this.Property(t => t.Credits).HasColumnName("Credits"); 
  18.         this.Property(t => t.DepartmentID).HasColumnName("DepartmentID"); 
  19.  
  20.         // Relationships 
  21.         this.HasMany(t => t.People) 
  22.             .WithMany(t => t.Courses) 
  23.             .Map(m => 
  24.                 { 
  25.                     m.ToTable("CourseInstructor"); 
  26.                     m.MapLeftKey("CourseID"); 
  27.                     m.MapRightKey("PersonID"); 
  28.                 }); 
  29.         this.HasRequired(t => t.Department) 
  30.             .WithMany(t => t.Courses) 
  31.             .HasForeignKey(d => d.DepartmentID); 
  32.     } 

我们可以看到,EF 的数据映射,那算是真正的数据映射。最基本的,其在内部无非是干了一件这样的事情:

数据库是哪个字段,对应的内存对象的属性是哪个属性。

最终,它都是通过一个对象工厂把领域模型生成出来,其原理大致如下:

  1. internal static Course BuildCourse(IDataReader reader) 
  2.     Course course = new Course(reader[FieldNames.CourseId]); 
  3.     contract.Title = reader[FieldNames.Title].ToString(); 
  4.     … 
  5.     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 的高效性。

Tags: 数据源架构 表入口

分享到: