注:本文是SOD框架原始碼倉庫的首頁介紹,原文地址
以前有一個著名的國產化妝品「大寶SOD密」,SOD框架雖然跟它沒有什麼關係,但是名字的確受到它的啟發,因為SOD框架就是給程式設計師準備的「蜜餈」(一種含有蜂蜜的餈粑),簡單靈活且非常容易「上手」。
SOD框架是一個全功能資料開發框架,框架的三大核心功能(SQL-MAP、ORM、Data Controls)代表三種資料開發模式(SQL開發模式/ORM開發模式/表單控制元件開發模式),這三大功能名稱的英文首字母縮寫也是SOD框架名稱的由來。SOD框架包含很多有用的功能元件,還包括多種企業級解決方案,以及相關的整合開發工具、圖書和社群支援。
當你用它來開發複雜的企業級專案的時候,你會感覺「愛不釋手」,因為不論你的團隊是什麼樣子的,SOD框架總能給您提供以最簡潔的方式實現最強大的功能,保證菜鳥級的程式設計師可以輕鬆看懂使用SOD框架編寫的每一行程式碼,也能讓資深程式設計師有一種「越野駕駛」的體驗--對資料存取細節全方位的掌控能力!
SOD框架脫胎於PDF.NET框架,框架追求的目標就是簡單與效率的平衡,體現在程式碼的精簡、開發維護的簡單與追求極致的執行效率。SOD框架目前執行在.NET平臺,但它沒有依賴於.NET框架很多獨有的特性,這使得SOD框架在理論上有可能實現跨語言平臺支援。
EF框架或大部分ORM框架的缺點就是SOD框架的優點, 因為SOD框架並不只是一個ORM框架。
ORM框架並不能解決所有的資料開發問題,如果試圖這麼做將大大增加ORM框架的複雜性和使用難度(比如用EF框架來做資料批次更新),所以這也是為什麼很多開發人員更加喜歡Dapper這類「微型ORM」(或半ORM)的原因。
在企業級資料開發的時候,有時候使用其它一些手段能夠起到更好的效果,這就要求框架要支援多種開發模式,支援更精準的操縱SQL查詢,更靈活的物件關係對映(ORM),更直接的面向底層資料存取,或者更高層面的資料抽象,需要全方位的資料開發解決方案。SOD框架擁有超過15年的專案應用歷史,相信它為你而生!
SOD框架包含SQL-MAP,ORM,DataControls三大子框架,但它卻是一個非常輕量級的框架,也是一個企業級資料應用開發的解決方案。
瞭解更多,請看這裡。
SOD框架特別適合於以下型別的企業專案:
因為足夠簡單,所以SOD框架是少數仍然支援 .NET 2.0的框架,當然,它也支援 .NET 3.x,.NET 4.x,.Net core 以及.NET 5/6等以上框架版本 。
SOD框架包括以下功能:
Hot Use Cache --熱快取(快取最常用的資料)
Binary Serialization --二進位制序列化
Query Log --查詢紀錄檔
Command Pipeline --命令管道
Distributed Identification --分散式ID
MVVM (Web/WinForm) --MVVM資料表單
Memory Database --記憶體資料庫
Transaction Log Data Replication --事務紀錄檔資料複製
Data Synchronization --資料同步
Distributed transaction --分散式事務
OData Client --OData 使用者端
Integrated Development Tool --整合開發工具,包括實體類生成、SQL-MAP程式碼自動生成和多種資料庫存取工具。
Nuget support --Nuget 支援
要了解更多,請看這篇文章:.NET ORM 的 「SOD蜜」--零基礎入門篇
或者參考框架作者編著的圖書:《SOD框架企業級應用資料架構實戰》,該書對SOD框架的企業級解決方案進行了詳細的介紹。。
在開始工作之前,先建立一個控制檯專案,然後在程式包管理控制檯,新增SOD框架的Nuget 包參照:
Install-Package PDF.NET.SOD
這樣即可獲取到最新的SOD框架包並且新增參照,然後,就可以開始下面的工作了。
已經建立好的當前Demo程式請看框架原始碼解決方案專案中的「SODTest」專案。
SOD框架預設使用應用程式組態檔中設定的最後一個資料連線設定,當然也可以不設定連線直接在程式中初始化資料連線物件。為了方便,我們在原始碼專案的app.config檔案中,做如下資料存取連線設定:
<connectionStrings>
<add name="local"
connectionString="Data Source=.;Initial Catalog=MyDB;Integrated Security=True"
providerName="SqlServer"/>
<add name="local2"
connectionString="Data Source=(LocalDB)\MSSQLLocalDB;AttachDbFilename=~/Database2.mdf;Integrated Security=True"
providerName="SqlServer" />
</connectionStrings>
providerName 是SOD框架的資料存取提供程式,PWMIS.Core.dll內建的可選簡略名稱有:
Access | SqlServer | Oracle | SqlCe | OleDb | Odbc
如果是其它的擴充套件程式集,那麼providerName應該寫成下面的形式:
providerName="<提供程式類全名稱>,<提供程式類所在程式集>"
比如使用SOD封裝過的Oracle官方的ADO.NET提供程式類:
providerName="PWMIS.DataProvider.Data.OracleDataAccess.Oracle,PWMIS.OracleClient"
在其它提供程式中,SOD框架提供了對【MySQL、Oracle、PostgreSQL、SQLite、達夢、人大金倉】等常見資料庫的支援(擴充套件程式集),只要資料庫提供了ADO.Net驅動程式,那麼SOD框架經過簡單包裝即可保證支援。
在當前範例中使用的是名字為「local2」的資料連線,你可以修改成你實際的連線字串,範例中使用的是SQLServer本地資料庫檔案,原始碼中已經包含了此檔案,如果不能使用請重新建立一個。
SOD框架使用ORM功能並不需要先定義實體類,直接使用與資料表對應的介面型別即可開始查詢。
下面定義一個用於資料存取的「使用者表」介面和對應的實體類。
public interface ITbUser
{
int ID { get; set; }
string Name { get; set; }
string LoginName { get; set; }
string Password { get; set; }
bool Sex { get; set; }
DateTime BirthDate { get; set; }
}
public class UserEntity : EntityBase, ITbUser
{
public UserEntity()
{
TableName = "TbUser";
IdentityName = "ID";
PrimaryKeys.Add("ID");
}
public int ID
{
get { return getProperty<int>("ID"); }
set { setProperty("ID", value); }
}
public string Name
{
get { return getProperty<string>("Name"); }
set { setProperty("Name", value,100); } //長度 100
}
public string LoginName
{
get { return getProperty<string>("LoginName"); }
set { setProperty("LoginName", value,50); }
}
public string Password
{
get { return getProperty<string>("Password"); }
set { setProperty("Password", value,50); }
}
public bool Sex
{
get { return getProperty<bool>("Sex"); }
set { setProperty("Sex", value); }
}
public DateTime BirthDate
{
get { return getProperty<DateTime>("BirthDate"); }
set { setProperty("BirthDate", value); }
}
}
用同樣的方式定義「訂單」介面ISimpleOrder、ISimpleOrderItem 和相應的實體類SimpleOrderEntity、SimpleOrderItemEntity。有關這幾個介面和型別的詳細定義請參見專案原始碼。
後設資料對映:
實體類的後設資料包括對映的表名稱、主外來鍵、標識欄位、屬性對映的欄位名稱和欄位型別、長度等。
在上面定義的實體類UserEntity中,它繼承了SOD框架的實體類基礎類別EntityBase,然後就可以在實體類的屬性定義中使用
getProperty方法和setProperty方法,這兩個方法都提供了「屬性欄位名」引數,它表示當前屬性名和資料表欄位名的對映關係。
在實體類建構函式中,TableName表示實體類對映的資料表名稱,IdentityName表示對映的資料表標識欄位名稱,一般用於自增欄位,PrimaryKeys表示實體類對映的主鍵欄位,可以有多個欄位來表示主鍵,例如聯合主鍵。
動態對映:
SOD框架的實體類採用「動態後設資料對映」,這些後設資料都是可以在程式執行時進行修改,因此它與Entity Framework等其它ORM框架的實體類對映方式有很大不同。這個特點使得實體類的定義和後設資料對映可以在一個類程式碼中完成,並且不依賴於.NET特性宣告。這種動態性使得SOD框架可以脫離繁瑣的資料庫表後設資料對映過程,簡化資料存取設定,並且能夠輕鬆的支援「分表、分庫」存取。
邏輯對映(虛擬對映):
後設資料的對映可以是「邏輯對映」,即對映一個資料表不存在的內容,例如指定要對映外來鍵欄位,但資料庫可以沒有物理的外來鍵欄位,或者指定一個虛擬的主鍵。也可以不做任何後設資料對映,這樣實體類可以作為一個類似的「字典」物件來使用,或者用於UI層資料物件。
動態實體類:
約定勝於設定。如果後設資料全部採用預設對映,可以大大簡化實體類的定義過程,直接採用介面型別來動態建立實體類和進行查詢存取。
在預設對映的時候,通常將型別名稱對映為表名稱,屬性名稱對映為欄位名稱。如果是介面型別,對映表名稱的時候會去掉介面名中第一個「I」字元。
例如介面型別 "IDbUser" 對映的表名稱為 "DbUser"
下面是根據介面動態建立實體類的範例:
IDbUser user = EntityBuilder.CreateEntity<IDbUser>();
注意這是一個可選步驟,如果你不使用Code First開發模式的話。
在很多ORM框架中,資料上下文物件DbContext使用都很常見,但對於SOD框架並不是必須的,SOD框架崇尚簡單直接的使用方式,有多種方式可以直接開始資料查詢。如果你需要使用Code First開發模式,由程式自動建立資料表而不是先設計資料庫表,可以使用SOD框架的資料上下文功能。
下面定義一個用於資料存取的簡單資料上下文物件SimpleDbContext。
public class SimpleDbContext : DbContext
{
public SimpleDbContext():base("local2")
{
}
protected override bool CheckAllTableExists()
{
CheckTableExists<UserEntity>();
CheckTableExists<SimpleOrderEntity>();
//建立表以後立即建立索引
InitializeTable<SimpleOrderItemEntity>("CREATE INDEX [Idx_OrderID] On [{0}] ([OrderID])");
return true;
}
}
在上面的程式碼中,SimpleDbContext使用名為「local2」的資料連線設定,它會在首次執行的時候呼叫CheckAllTableExists方法,檢查表是否存在,如果不存在則建立實體類對應的資料表。
可以使用InitializeTable方法在表第一次建立完成以後執行表的初始化工作,比如建立索引。
AdoHelper物件是SOD框架的資料存取輔助物件,它是一個抽象資料存取物件,需要根據具體的資料庫型別提供相應的資料存取提供程式。任何資料庫的.NET驅動程式經過簡單包裝即可成為SOD框架的資料存取提供程式。
SOD框架內建的資料存取提供程式型別請參考本文【1.1 設定資料連線】的內容。
AdoHelper物件提供了各種資料存取方法,包括獲取DataSet、DataReader以及執行資料的增刪改、呼叫儲存過程和多級事務查詢等。
範例化一個AdoHelper物件可以通過MyDB類的多種方式來實現,也可以根據連線設定來動態建立,或者直接範例化一個SOD資料存取提供程式。下面的例子提供了三種方式來範例化AdoHelper物件:
//根據連線設定中的「連線名字」來建立,例如"local2"
AdoHelper db1 = MyDB.GetDBHelperByConnectionName("local2");
//根據連線設定中最後一個設定來建立
AdoHelper db2 = MyDB.GetDBHelper();
//直接範例化資料存取提供程式,例如SqlServer資料庫
AdoHelper db3= SqlServer();
下面是使用AdoHelper的簡單範例。
通過呼叫ExecuteNonQuery方法來實現,該方法返回本次執行受影響的行數的結果值。
下面是一個建立使用者表的例子,建立的使用者表在下面的範例中會使用到:
AdoHelper db2 = AdoHelper.CreateHelper("local2");
//例外處理範例
string sql_createUser = @"
Create table [TbUser](
[ID] int identity primary key,
[Name] nvarchar(100),
[LoginName] nvarchar(50),
[Password] varchar(50),
[Sex] bit,
[BirthDate] datetime
)";
try
{
db2.ExecuteNonQuery(sql_createUser);
Console.WriteLine("表[TbUser] 建立成功!");
}
catch (PWMIS.DataProvider.Data.QueryException qe)
{
Console.WriteLine("SOD查詢錯誤,錯誤原因:{0}", qe.InnerException.Message);
}
catch (Exception ex)
{
Console.WriteLine("錯誤:{0}", ex.Message);
}
使用AdoHelper物件進行查詢的時候,如果執行查詢發生異常,使用例外處理程式碼可以捕獲QueryException查詢異常物件,
在該異常物件中可以檢視詳細的錯誤原因,以及執行查詢相關的一些資訊,如果SQL語句、執行引數等。
SOD框架認為保證資料存取的安全是框架最重要的目標,引數化查詢是避免「SQL隱碼攻擊」最有效的手段。SOD框架對所有資料庫都支援引數化查詢,包括Access資料庫等。
除了最基礎的AdoHelper物件可以支援引數化查詢,SQL-MAP功能也是支援引數化查詢的。下面是使用引數化查詢插入使用者資料的例子:
string sql_insert = "INSERT INTO [TbUser] ([Name],[LoginName],[Password],[Sex],[BirthDate]) VALUES(@Name,@LoginName,@Password,@Sex,@BirthDate)";
IDataParameter[] paras = new IDataParameter[] {
db2.GetParameter("Name","張三"),
db2.GetParameter("LoginName","zhangsan"),
db2.GetParameter("Password","888888"),
db2.GetParameter("Sex",true),
db2.GetParameter("BirthDate",new DateTime(1990,2,1))
};
int rc = db2.ExecuteNonQuery(sql_insert, CommandType.Text, paras);
if (rc > 0)
Console.WriteLine("插入資料成功!使用者名稱:{0}", paras[0].Value);
AdoHelper物件的GetParameter方法有多個過載方法,可以滿足引數化查詢的各種需求,它返回的是當前資料庫型別的查詢引數物件,可以實現更多的引數化查詢的細節控制,例如呼叫儲存過程所需的查詢引數。
微型ORM的特點是允許直接執行SQL語句查詢,但是查詢結果可以直接對映成為POCO型別或者實體型別,
相比較以前執行查詢返回一個資料集(DataSet)而言,微型ORM即享受了直接編寫SQL執行查詢的靈活性,又得到了強型別物件使用的便利性,因此微型ORM很受開發人員歡迎。
SOD框架支援微型ORM功能,它允許你直接控制查詢返回的DataReader物件,也可以將查詢直接對映到一個強型別物件。下面的範例演示了通過微型ORM功能查詢得到使用者列表物件。
string sql_query = "SELECT [ID],[Name],[Sex],[BirthDate] FROM [TbUser] WHERE [LoginName]={0}";
var mapUsers = db2.ExecuteMapper(sql_query, "zhangsan")
.MapToList(reader => new
{
ID = reader.GetInt32(0),
Name = reader.GetString(1),
Sex = reader.GetBoolean(2),
BirthDate = reader.GetDateTime(3)
});
var userList = db2.QueryList<UserInfo>(sql_query, "zhangsan");
上面的範例演示了兩者結果對映方式,使用AdoHelper物件的MapToList方法可以直接操作返回的DataReader物件,根據SQL語句中的欄位順序客製化讀取查詢結果欄位值,這種方式由於是面向底層Ado.NET的操作,因此查詢具有很高的效能。
另外一種方式就是使用AdoHelper物件的QueryList方法,它直接將SQL查詢結果對映為一個POCO物件。
上面的範例還演示了SOD的微型ORM查詢使用的「引數化查詢」方式,相比較於直接的引數化查詢,這裡只需要使用引數的預留位置來表示引數,就像Console.WriteLine()方法使用的引數一樣。例如上面的範例中查詢的引數是欄位LoginName對於的查詢引數,引數值是「zhangsan」。
引數化查詢的SQL語句可以被資料庫編譯執行從而提高查詢效率;引數化查詢是在資料庫完成 SQL 指令的編譯後,才套用引數執行,因此就算引數中含有危害資料庫的指令,也不會被資料庫所執行,因此引數化查詢提高了SQL執行的安全性。
雖然引數化查詢有很多優點,但缺點是在開發上會增加程式碼編寫量,另外由於引數化查詢的SQL語句缺乏統一的標準,也會使得采用引數化查詢的程式碼難以在不同資料庫平臺之間移植。請看下面的範例:
--Microsoft SQL Server
INSERT INTO myTable (c1, c2, c3, c4) VALUES (@c1, @c2, @c3, @c4)
--Microsoft Access
UPDATE myTable SET c1 = ?, c2 = ?, c3 = ? WHERE c4 = ?
--MySQL
UPDATE myTable SET c1 = ?c1, c2 = ?c2, c3 = ?c3 WHERE c4 = ?c4
--Oracle
UPDATE myTable SET c1 = :c1, c2 = :c2, c3 = :c3 WHERE c4 = :c4
--PostgreSQL
UPDATE myTable SET c1 = $1, c2 = $2, c3 = $3 WHERE c4 = $4
從上面不同資料庫的SQL引數化查詢的範例可以看到,不同資料庫支援的引數查詢的引數名寫法是不同的,要求在引數名前面加不同的字首符號(@/