以 EF 為代表的基於 Linq 的 ORM 框架總是 很重。
他們的功能早已超出了一個 ORM 的范疇,
ORM 是 Object Relational Mapping ,從名字上看,其初衷是將 數據庫中的字段 與 實體中的屬性 進行關聯映射,
但是 重型 ORM 框架 做了很多 額外 的事情 :
- 數據庫連接
- 數據庫事務包裝
- 實體緩存
- 實體關聯管理
- 數據庫表同步
- 這些功能很好,
- 強大的功能往往是死板的,
我們無法編寫那些靈活的 Sql 去實現某些簡便的操作。
以 MyBatis.NET、Dapper 為代表的,
則是基於開發者自行編寫 Sql 的 ORM 框架又 太輕。
因為是自行編寫 Sql ,
所以他們非常靈活,
但是用起來很 痛苦。
哪怕是一個簡單的 Insert ,Update 也得寫 Sql,
而且還無法擺脫 數據庫 兼容的問題。
你所編寫的那些 Sql 在大部分情況下,只能用於一種 數據庫
今天要向大家介紹一個 輕量級、不用寫 Sql、可以兼容多數據庫 的 ORM 框架
Reface.NPI
什么是 NPI
NPI 全名 .Net Persistent Interface 。
這是一個利用 interface 實現的輕量級 ORM 框架,
它與市面上大多數的 ORM 框架不同,它不基於 Linq 進行數據庫操作,而是基於 Method Name。
例如
IList<User> SelectById(int id);
IList<User> SelecyByNameLike(string name);
void UpdatePasswordById(int id, string password);
bool DeleteById(int id);
NPI 提供了將上述 MethodName 和 實際運算時的入參 生成 Sql執行信息 的方法。
此庫不實現以下功能
- 通過 AOP 產生 interface 的 Proxy ( 這個功能會在 Reface.AppStarter.NPI 中基於 Castle.DynamicProxy 實現 )
- 對 Sql執行信息 的執行
- 將查詢結果映射到對應的實體中 ( 這個功能會在 Reface.AppStarter.NPI 以基於 Dapper 實現 )
- 對事務的管理 ( 這個功能預計在一個由 Reface.AppStarter 構建的一個業務框架中實現 )
不建議直接將此庫用於業務功能的開發,
建議對該庫進行一定的二次開發或封裝后再投入使用,
開發者可以根據系統當前已經依賴的庫進行封裝。
計划在未來轉為 .NetStandard 版本,以同時提供給 .NetCore 使用。
依賴項
- Reface.StateMachine ( 庫中對方法名稱解析的過程依賴於此庫 )
- Reface ( 提供了一些基礎的功能和方法,使用 .NetStandard2.0 編寫 )
由於使用了 .NetStrand2.0,因此本庫需要 .Net framework 4.6.1 及以上版本才能使用。
使用方法
四個分析器
系統中針對四種數據庫不同的操作(增刪改查),分別提供了四個不同的語義分析器
- ISelectParser
- IInsertParser
- IUpdateParser
- IDeleteParser
這四種轉化器能夠將一個字符串分析成為結構化的數據庫處理結構,如
ISelectParser parser = new DefaultSelectParser();
string command = "ByIdAndName";
SelectInfo info = parser.Parse(command);
// info.Fields = [];
// info.Conditions[0].Field = "Id";
// info.Conditions[1].Field = "Name";
// info.Orders = [];
四種分析器分別能生成四種不同的語句結構:
Parser | 結果的類型 |
---|---|
ISelectParser | SelectInfo |
IInsertParser | InsertInfo |
IUpdateParser | UpdateInfo |
IDeleteParser | DeleteInfo |
這些 xxxInfo 的結構並不復雜,這里將不對其展開進行更多的介紹。
四個分析器的整合
ICommandParser 對四個分析器做了整合,以便我們不關心對方法的區分而直接得到 ICommandInfo 。
SelectInfo , InsertInfo , UpdateIfo 和 DeleteInfo 都實現了 ICommandInfo 接口。
ICommandParser 通過方法的第一個單詞對方法名進行分類,哪些前綴屬於查詢、哪些前綴屬於更新,都是由它的實現的。
庫中的 DefaultCommandParser 按照下面的 首單詞 進行邏輯區分 :
查詢語句
- Get
- Select
- Fetch
- Find
- PagingGet
- PagingSelect
- PagingFetch
- PagingFind
新增語句
- Insert
- New
- Create
更新語句
- Update
- Modify
刪除語句
- Delete
- Remove
下面的例子是分析了一個更新語句。
ICommandInfo 中的 Type 字段有助於你判斷應當將 ICommandInfo 轉化為一個具體的 Info 。
string command = "UpdateNameById";
ICommandParser parser = new DefaultCommandParser();
ICommandInfo info = parser.Parse(command);
if (info.Type == CommandInfos.Update)
UpdateInfo updateInfo = (UpdateInfo)info;
// updateInfo.SetFields[0].Field = "Name";
// updateInfo.Conditions[0].Field = "Id";
通過方名和參數生成執行信息
執行信息包含兩個信息
- Sql 語句
- Sql 參數
在庫中,生成執行信息是由 ISqlCommandGenerator 完成的。
// ISqlCommandGenerator.cs
using System.Reflection;
namespace Reface.NPI.Generators
{
public interface ISqlCommandGenerator
{
SqlCommandDescription Generate(MethodInfo methodInfo, object[] arguments);
}
}
設計該接口的初衷是希望使用方是以 AOP 的方式攔截某個方法的執行,
並將 MethodInfo 和 攔截到的入參 傳遞給 ISqlCommandGenerator,
再根據生成的執行信息直接執行,得到結果。
目前庫中有一個它的實現類型 : DefaultSqlServerCommandGenerator 。
你會從名字上發現,它是面向 SqlServer 的實現,
很明顯,不同的 數據庫 往往支持的語句並不相同。
因此,為不同的 數據庫 編寫不同的 ISqlCommandGenerator 是有必要的。
SqlCommandDescripion 是一個簡單的數據結構,它包含 SqlCommand 和 Parameters 兩個主要的屬性,使用這兩個屬性可以完成后續的 Sql 執行。
注意事項
- 對於表名的獲取,是基於 INpiDao<TEntity> 中的 TEntity 來完成的,
通過反射 TEntity 上的 System.ComponentModel.DataAnnotations.Schema.TableAttibute 特征來獲取表名。 - 由於 TableAttribute 在很多常見庫中出現,所以要注意不要引用錯了。
- Reface.NPI 允許你重寫對表名的獲取,對字段的獲取等邏輯,重寫方法會在后面的文章中介紹。
方法名及預期 Sql 參照表
方法名稱 | 期望的 Sql | 說明 |
---|---|---|
SelectById | select * from [table] where id = ? | 以 Id 作為條件查詢實體 |
SelectNameAndAgeById | select name, age from [table] where id = ? | 以 Id 作為條件,只查詢 Name 和 Age 字段 |
SelectByRegistertimeGreaterthan | select * from [table] where Registertime > ? | 查詢 RegisterTime 大於參數的實體 |
SelectByRegistertimeGteq | select * from [table] where Registertime >= ? | 查詢 RegisterTime 大於等於參數的實體 |
SelectByIdAndName | select * from [table] where id = ? and name = ? | 按 Id 且 Name 作為條件查詢實體 |
SelectByIdOrName | select * from [table] where id = ? or name = ? | 以 Id 或 Name 作為條件查詢實體 |
SelectByIdOrNameLike | select * from [table] where id = ? or name like ? | 以 Id 或 Name Like 作為條件查詢實體 |
SelectByIdOrderbyName | select * from [table] where id = ? order by name | 以 Id 查詢並以 Name 排序 |
SelectByIdOrderByNameDesc | select * from [table] where id = ? order by name desc | 以 Id 作為條件並以 Name 倒序排序 |
DeleteById | delete from [table] where id = ? | 以 Id 作為條件刪除 |
UpdatePasswordById | update [table] set password = ? where id = ? | 以 Id 作為條件更新 Password |
UpdatePasswordByNameLike | update [table] set password = ? where name like ? | 以 Name Like 作為條件更新 Password |
UpdateStateAndTokenByLastoprtimeGt | update [table] set state=?,token=? where lastoprtime > ? | 以 LastOprTime 大於 作為條件更新 state 和 token |
UpdateById | update [table] set ... where id = ? | 以 Id 作為條件,並以 Id 以外的字段作為 Set 子句 |
UpdateWithoutCreatetimeById | update [table] set ... where id = ? | 以 Id 作為條件,並以 Id 和 Createtime 以外的字段作為 Set 子句 |
UpdateWithoutStateCreatetimeById | update [table] set ... where id = ? | 以 Id 作為條件,並以 Id 、State 和 Createtime 以外的字段作為 Set 子句 |
InsertWithoutIdCreatetime(Entity) | insert into [table] (...) values(?,...,?) | 排除 Id 和 Createtime 字段新增實現 |
相關鏈接
下期預告 : 《NPI 方法規則詳解》
將詳細地說明四種操作所支持的各種操作。