注:本文是SOD框架原始碼倉庫的首頁介紹,原文地址
一、框架介紹
1,SOD框架是什么?
以前有一個著名的國產化妝品“大寶SOD密”,SOD框架雖然跟它沒有什么關系,但是名字的確受到它的啟發,因為SOD框架就是給程式員準備的“蜜糍”(一種含有蜂蜜的糍粑),簡單靈活且非常容易“上手”,
SOD框架是一個全功能資料開發框架,框架的三大核心功能(SQL-MAP、ORM、Data Controls)代表三種資料開發模式(SQL開發模式/ORM開發模式/表單控制元件開發模式),這三大功能名稱的英文首字母縮寫也是SOD框架名稱的由來,SOD框架包含很多有用的功能組件,還包括多種企業級解決方案,以及相關的集成開發工具、圖書和社區支持,
當你用它來開發復雜的企業級專案的時候,你會感覺“愛不釋手”,因為不論你的團隊是什么樣子的,SOD框架總能給您提供以最簡潔的方式實作最強大的功能,保證菜鳥級的程式員可以輕松看懂使用SOD框架撰寫的每一行代碼,也能讓資深程式員有一種“越野駕駛”的體驗--對資料訪問細節全方位的掌控能力!
SOD框架脫胎于PDF.NET框架,框架追求的目標就是簡單與效率的平衡,體現在代碼的精簡、開發維護的簡單與追求極致的運行效率,SOD框架目前運行在.NET平臺,但它沒有依賴于.NET框架很多獨有的特性,這使得SOD框架在理論上有可能實作跨語言平臺支持,
2,為什么需要SOD框架?
EF框架或大部分ORM框架的缺點就是SOD框架的優點, 因為SOD框架并不只是一個ORM框架,
ORM框架并不能解決所有的資料開發問題,如果試圖這么做將大大增加ORM框架的復雜性和使用難度(比如用EF框架來做資料批量更新),所以這也是為什么很多開發人員更加喜歡Dapper這類“微型ORM”(或半ORM)的原因,
在企業級資料開發的時候,有時候使用其它一些手段能夠起到更好的效果,這就要求框架要支持多種開發模式,支持更精準的操縱SQL查詢,更靈活的物件關系映射(ORM),更直接的面向底層資料訪問,或者更高層面的資料抽象,需要全方位的資料開發解決方案,SOD框架擁有超過15年的專案應用歷史,相信它為你而生!
SOD框架包含SQL-MAP,ORM,DataControls三大子框架,但它卻是一個非常輕量級的框架,也是一個企業級資料應用開發的解決方案,
了解更多,請看這里,
SOD框架特別適合于以下型別的企業專案:
- 對資料操作安全有嚴格要求的金融行業;
- 對資料訪問速度、記憶體和CPU資源有苛刻要求的互聯網行業;
- 對需求常常變化,專案經常迭代,要求快速開發上線的專案;
- 對穩定性要求高,需要長期維護的企業級應用如MIS、ERP、MES等行業;
- 需要低成本開發,人員技能偏低的中小型專案,
因為足夠簡單,所以SOD框架是少數仍然支持 .NET 2.0的框架,當然,它也支持 .NET 3.x,.NET 4.x,.Net core 以及.NET 5/6等以上框架版本 ,
3,功能特性
SOD框架包括以下功能:
3.1 核心三大功能 -(S,O,D):
- SQL-MAP :
- XML SQL config and Map DAL --基于XML配置的SQL查詢和資料訪問層映射
- SQL Map Entity --SQL陳述句映射為物體類
- ORM :
- OQL(ORM Query Language) --ORM查詢語言:OQL
- Data Container --資料容器
- Entity Indexer --物體類索引器訪問
- Table Map route Query --分表查詢支持
- Micro ORM --微型ORM
- Data Controls :
- Consistent Data Froms --一致的資料表單訪問技術
- WebForm Data Controls --Web表單資料控制元件
- WinForm Data Controls --Windows表單資料控制元件
3.2 有用的功能組件:
Hot Use Cache --熱快取(快取最常用的資料)
Binary Serialization --二進制序列化
Query Log --查詢日志
Command Pipeline --命令管道
Distributed Identification --分布式ID
3.3 企業級解決方案:
MVVM (Web/WinForm) --MVVM資料表單
Memory Database --記憶體資料庫
Transaction Log Data Replication --事務日志資料復制
Data Synchronization --資料同步
Distributed transaction --分布式事務
OData Client --OData 客戶端
3.4 工具:
Integrated Development Tool --集成開發工具,包括物體類生成、SQL-MAP代碼自動生成和多種資料庫訪問工具,
Nuget support --Nuget 支持
4,原始碼和社區:
- Code: https://github.com/znlgis/sod or https://gitee.com/znlgis/sod
- Home: http://www.pwmis.com/sqlmap
- Blog: https://www.cnblogs.com/bluedoctor
- QQ Group:18215717,154224970
要了解更多,請看這篇文章:.NET ORM 的 “SOD蜜”--零基礎入門篇
或者參考框架作者編著的圖書:《SOD框架企業級應用資料架構實戰》,該書對SOD框架的企業級解決方案進行了詳細的介紹,,
二、快速入門
1,準備作業
在開始作業之前,先建立一個控制臺專案,然后在程式包管理控制臺,添加SOD框架的Nuget 包參考:
Install-Package PDF.NET.SOD
這樣即可獲取到最新的SOD框架包并且添加參考,然后,就可以開始下面的作業了,
已經建立好的當前Demo程式請看框架原始碼解決方案專案中的“SODTest”專案,
1.1,配置資料連接:
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本地資料庫檔案,原始碼中已經包含了此檔案,如果不能使用請重新創建一個,
1.2,物體類定義
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>();
1.3,定義資料背景關系
注意這是一個可選步驟,如果你不使用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方法在表第一次創建完成以后執行表的初始化作業,比如創建索引,
2,資料訪問輔助物件
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的簡單示例,
2.1,執行不回傳值的查詢
通過呼叫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陳述句、執行引數等,
2.2,引數化查詢
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方法有多個多載方法,可以滿足引數化查詢的各種需求,它回傳的是當前資料庫型別的查詢引數物件,可以實作更多的引數化查詢的細節控制,例如呼叫存盤程序所需的查詢引數,
2.3,微型ORM
微型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”,
3,SQL-MAP簡介
3.1,引數化查詢的難題
引數化查詢的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引數化查詢的示例可以看到,不同資料庫支持的引數查詢的引數名寫法是不同的,要求在引數名前面加不同的前綴符號(@/???/$),
在ADO.NET中,采用各資料庫的OleDB或者ODBC驅動程式,都要求使用 ?符號表示引數,
3.2,抽象SQL引數化查詢
對于引數化查詢SQL陳述句寫法不統一的問題,只需要再抽象出一種引數化查詢的方式即可解決這個問題,在SOD框架中,對引數的定義統一采用##來處理,具體格式如下:
引數名字[:引數型別],[資料型別],[引數長度],[引數輸出輸入型別]#
上面定義當中,中括號里面的內容都是可選的,引數定義的詳細內容,請參看PDF.NET(PWMIS資料開發框架)之SQL-MAP目標和規范
采用抽象SQL引數查詢,根據引數定義規范,上面對于myTable的更新陳述句可以寫成下面的樣子:
UPDATE myTable SET
c1 = #c1#,
c2 = #c2:String#,
c3 = #c3:String,Sring,50#
WHERE c4 = #c4:Int32#
如果不指定引數的型別,默認為String型別,例如c1引數,
程式在運行時,會根據當前具體的資料庫訪問程式實體,將##內部的引數替換成合適的引數內容,
3.3,抽象SQL查詢:SQL-MAP技術
我們將SQL中的引數“抽象化”了,我們還可以進一步抽象整個SQL,看下面的抽象程序:
1, 撰寫任意形式的合法SQL查詢陳述句;
2, 抽象SQL中的引數;
3, 將整個SQL陳述句抽象成一個唯一名字為CommandName;
4, 將一組CommandName映射到一個DAL類檔案;
5, 將這個CommandName映射到一個DAL類的方法名稱;
6, 將SQL陳述句中的引數名稱映射到該DAL類的當前方法中的引數名稱;
7, 將整個SQL腳本檔案映射到一個DAL程式集,
這個思想,就是SQL-MAP,將SQL陳述句映射為程式!
下面,我們用一個學生成績管理的例子作為SQL-MAP的SQL陳述句寫法的示例:
<CommandClass Name ="ScoreManagement" Description ="成績管理" Class ="" >
<Select CommandName="GetStudent" CommandType="Text" Description="查詢所屬系的學生資訊" Method="" Result>
<![CDATA[
select * from Student where deptID=#DID:Int32#
]]>
</Select>
<Insert CommandName="InsertStudent" CommandType="Text" Description="增加學生" Method="AddStudent" >
insert into [Student](stuName,deptID) values(#Name:String#,#DeptId:Int32#)
</Insert>
</CommandClass>
將上面的XML保存為一個名字叫做SqlMap.config的組態檔,該組態檔的詳細內容請參考SOD框架原始碼解決方案里面SqlMapDemo專案中的代碼,
在上面的SQL-MAP組態檔中,CommandClass節點對應于程式的DAL層查詢類,一個查詢類下面有多種查詢型別,包括Select/Update/Inset/Delete這四種SQL查詢型別,分別對應于
CommandClass節點下面的Select/Update/Insert/Delete型別,如上所述,Select節點有一個命令名為GetStudent,Insert節點有一個命令名為InsertStudent,
這些命令名字分別對應于ScoreManagement類下面的方法,
3.4,SQL查詢和代碼映射
下面看一下這個SQL-MAP配置映射的DAL(資料訪問層)類的具體代碼:
using System;
using System.Data;
using System.Collections.Generic;
using PWMIS.DataMap.SqlMap;
using PWMIS.DataMap.Entity;
using PWMIS.Common;
namespace SqlMapDemo.SqlMapDAL
{
public partial class ScoreManagement
: DBMapper
{
public ScoreManagement()
{
Mapper.CommandClassName = "ScoreManagement";
Mapper.EmbedAssemblySource="SqlMapDemo,SqlMapDemo.SqlMap.config";
}
/// <summary>
/// 查詢所屬系的學生資訊
/// </summary>
/// <param name="DID"></param>
/// <returns></returns>
public DataSet GetStudent(Int32 DID )
{
//獲取命令資訊
CommandInfo cmdInfo=Mapper.GetCommandInfo("GetStudent");
//引數賦值,推薦使用該種方式;
cmdInfo.DataParameters[0].Value = https://www.cnblogs.com/bluedoctor/archive/2023/07/12/DID;
//引數賦值,使用命名方式;
//cmdInfo.SetParameterValue("@DID", DID);
//執行查詢
return CurrentDataBase.ExecuteDataSet(CurrentDataBase.ConnectionString, cmdInfo.CommandType, cmdInfo.CommandText , cmdInfo.DataParameters);
//
}//End Function
/// <summary>
/// 增加學生
/// </summary>
/// <param name="Name"></param>
/// <param name="DeptId"></param>
/// <returns></returns>
public Int32 AddStudent(String Name , Int32 DeptId )
{
//獲取命令資訊
CommandInfo cmdInfo=Mapper.GetCommandInfo("InsertStudent");
//引數賦值,推薦使用該種方式;
cmdInfo.DataParameters[0].Value = https://www.cnblogs.com/bluedoctor/archive/2023/07/12/Name;
cmdInfo.DataParameters[1].Value = DeptId;
//引數賦值,使用命名方式;
//cmdInfo.SetParameterValue("@Name", Name);
//cmdInfo.SetParameterValue("@DeptId", DeptId);
//執行查詢
return CurrentDataBase.ExecuteNonQuery(CurrentDataBase.ConnectionString, cmdInfo.CommandType, cmdInfo.CommandText , cmdInfo.DataParameters);
//
}//End Function
}//End Class
}//End NameSpace
上面的SqlMapDal代碼完整的映射了SQL-MAP組態檔的內容,實作了SQL陳述句抽象到程式抽象的程序,因此理論上只要會寫SQL陳述句即可寫DAL查詢程式,
這個SqlMapDal代碼檔案可以通過SOD框架的集成開發工具自動生成,每次修改了SQL-MAP組態檔,可以通過工具“一鍵生成DAL代碼”,極大的增加了DAL層開發的靈活性和開發效率,
4,ORM查詢簡介
SOD框架的ORM功能支持普通的基于物體類的CRUD功能,還有框架獨特的ORM查詢語言--OQL,其中OQL又分為面向單表操作的泛型OQL(GOQL)和支持多表復雜查詢的OQL,
4.1,ORM查詢語言--OQL
使用ORM必然會提到物體類的話題,物體類實作了程式物件和資料表結構的映射,在很多ORM框架的實作中,物體類又分為“充血物體類”和“貧血物體類”兩種,后者一般就是簡單的POCO型別,而前者需要復雜的定義以支持物體類保持資料的狀態,以便生成正確的SQL陳述句;對于前者必須使用另外的方式在代碼編譯期間生成SQL陳述句,
ORM本來是完成“物件-關系映射”的,但這里大多數的ORM都包含了“生成SQL”的功能,而要實作SQL那樣的靈活性,那么我們必須分離出ORM這個關注點,將“生成SQL”的功能從ORM中抽取出來,這樣我們就能夠有更多的精力致力于發明一個面向物件的,用于ORM查詢的語言,(ORM Query Language) ,這就是OQL,
ORM查詢語言,其實早就有了,從早期的Hibernate的HQL,到MS的Linq(Linq2SQL,EF其實內部都是使用Linq生成的SQL),它們都可以生成復雜的SQL陳述句,它們都是直接作用于ORM框架的,幾乎在與Linq同一時期,SOD框架也發明了自己的ORM查詢語言,稱為OQL,下面提到的OQL,都是指的SOD框架的OQL,
有關OQL的由來以及OQL的詳細語法和使用示例,請參考以下幾篇文章:
- ORM查詢語言(OQL)簡介--概念篇
- ORM查詢語言(OQL)簡介--實體篇
- ORM查詢語言(OQL)簡介--高級篇:脫胎換骨
- ORM查詢語言(OQL)簡介--高級篇(續):廬山真貌
4.2,GOQL示例
GOQL適合單表查詢,可以僅定義一個對應資料表的介面型別來進行GOQL查詢,下面的例子使用ITbUser介面來查詢用戶資料:
//GOQL簡單示例
//GOQL使用介面型別進行查詢
var goql = OQL.FromObject<ITbUser>()
.Select()
.Where((cmp, obj) => cmp.Comparer(obj.LoginName, "=", "zhangsan"))
.END;
var list1 = goql.ToList(db2);
//GOQL使用物體型別別進行查詢
var list11 = OQL.FromObject<UserEntity2>()
.Select()
.Where((cmp, obj) => cmp.Comparer(obj.LoginName, "=", "zhangsan"))
.END
.ToList(db2);
//GOQL復雜示例
var list2 = OQL.FromObject<ITbUser>()
.Select(s => new object[] { s.ID, s.Name, s.Sex, s.BirthDate }) //選取指定欄位屬性查詢
.Where((cmp, obj) => cmp.Property(obj.LoginName) == "zhangsan") //使用運算子多載的條件比較
.OrderBy((order, obj) => order.Desc(obj.ID))
.ToList(db2);
OQL運算式總是以From方法開始,以END屬性結束,通過OQL的鏈式語法,Select、Where、OrderBy方法總是以固定的順序出現,因此無任何SQL撰寫經驗的人也可以通過OQL寫出正確的查詢,
GOQL物件通過呼叫OQL的FormObject泛型方法得到,之后的語法跟OQL一樣,最后通過GOQL的ToList方法執行查詢得到結果,
ToList方法有多載,可以用一個AdoHelper物件做引數來對指定的資料庫進行查詢,
GOQL的復雜查詢支持通過Select方法指定要查詢的物體類屬性欄位,也可以在Where、OrderBy方法上使用Lambda運算式購置查詢和排序條件,
Where方法上使用了OQLCompare物件來生成查詢條件物件,它有多種條件比較方法和方法多載,也支持運算子多載,
4.3,OQL示例
GOQL實際上式OQL的簡化形式,所以OQL可以實作更強大的查詢方式,除了支持單表/單物體類查詢,也支持多表/多物體類查詢,
OQL物件的Form方法需要一個或者多個物體類物件實體作為引數,因此在構造OQL運算式的時候可以訪問當前物體類物件的屬性從而簡化條件運算式的構造,
下面是用戶登錄查詢的示例,在資料庫中查詢是否有匹配的登錄名和密碼的記錄:
//OQL查詢示例
UserEntity ue = new UserEntity();
ue.LoginName = "zhangsan";
ue.Password = "888888";
//OQL簡單查詢示例
var oql = OQL.From(ue)
.Select()
.Where(ue.LoginName, ue.Password)
.END;
var userObj = EntityQuery<UserEntity>.QueryObject(oql, db2);
上面的查詢中直接使用了UserEntity物件的LoginName、Password屬性的值作為查詢條件值,如果使用GOQL,上面的查詢等價于下面的查詢方式:
var userObj2 = OQL.FromObject<UserEntity>()
.Select()
.Where((cmp, obj) => cmp.Comparer(obj.LoginName, "=", "zhangsan") &
cmp.Comparer(obj.Password, "=", "888888"))
.END
.ToObject(db2);
var userObj3 = OQL.FromObject<UserEntity>()
.Select()
.Where((cmp, obj) => cmp.Property(obj.LoginName)== "zhangsan" &
cmp.Property(obj.Password) == "888888")
.END
.ToObject(db2);
在上面的查詢中,Where方法里面使用了兩個比較條件,OQL使用&符號表示SQL的AND條件,使用|符號表示SQL的OR條件,
這個功能使用了C#語言的運算子多載功能來實作的,OQL還支持>,<,==,!=,>=,<= 這些運算子多載,運算子多載功能的支持使得OQL運算式撰寫更簡單直觀,
除了上面介紹的OQL簡單查詢,OQL也支持復雜查詢,請看下面的示例:
//OQL復雜查詢示例
var oql2 = OQL.From(ue)
.Select(new object[] { ue.ID, ue.Name, ue.Sex, ue.BirthDate })
.Where(cmp => cmp.Property(ue.LoginName) == "zhangsan" & cmp.EqualValue(ue.Password))
.OrderBy(order => order.Desc(ue.ID))
.END;
oql2.Limit(5, 1);
var list4 = EntityQuery<UserEntity>.QueryList(oql2, db2);
實際上這個查詢并不算復雜,OQL還支持主子表查詢、多表查詢,由于篇幅原因不在這里做更多介紹,
OQL除了從資料庫查詢資料到物體類物件,還支持批量更新操作,例如更新所有用戶的密碼為“8888”:
UserEntity ue= new UserEntity();
ue.Password = "8888";
var oql_update = OQL.From(ue).Update(ue.Password).END;
var ru = EntityQuery<UserEntity>.ExecuteOql(oql_update, db2);
4.4,增刪改資料
由于SOD框架采用的是充血物體類,物體類可以記錄前后操作的資料狀態,使得撰寫增刪改功能的代碼很簡單,
如下面的對用戶資料的增刪改程序:
UserEntity ue2 = new UserEntity();
ue2.LoginName = "lisi";
ue2.Password = "8888";
ue2.Name = "李四";
int ic= EntityQuery<UserEntity>.Instance.Insert(ue2, db2);
if(ic>0)
Console.WriteLine("添加資料成功,用戶ID={0}", ue2.ID);
ue2.BirthDate = new DateTime(1990, 1, 2);
ue2.Sex = false ;
int uc=EntityQuery<UserEntity>.Instance.Update(ue2, db2);
if(uc>0)
Console.WriteLine("修改資料成功,用戶ID={0}", ue2.ID);
ue2.PrimaryKeys.Clear();
ue2.PrimaryKeys.Add("LoginName");
int dc= EntityQuery<UserEntity>.Instance.Delete(ue2, db2);
if (dc > 0)
Console.WriteLine("洗掉用戶[{0}]成功!",ue2.LoginName);
在用戶表中ID欄位是標識欄位,因此插入資料的時候框架會忽略標識欄位的值,但是插入資料成功后物體類中標識欄位對應的屬性會自動獲取剛才的自增欄位值,
如上面的例子,當ue2物件插入成功后,ue2.ID就獲得了剛剛插入的自增欄位值,同時,ue2物件的ID屬性也是物體類的主鍵欄位屬性,
這樣當物體類物件修改后,可以呼叫EntityQuery實體物件的Update方法更新資料,
SOD框架物體類的元資料是動態元資料,物體類的主鍵欄位是虛擬的,并不需要跟資料表的主鍵一一對應,所以程式可以在運行時修改物體類的主鍵欄位,
如上述示例程式,ue2物件清除了自己的主鍵欄位,添加了用戶登錄名欄位,之后成功洗掉了前面添加的資料,
除了采用物體類的方式類增刪改資料,SOD框架還支持批量增刪改資料,使用OQL陳述句即可實作這個功能,可以參考前面OQL的示例,
5,示例:保存和查詢訂單資料
前面介紹了SOD框架ORM操作的一些基礎知識,實際上,框架提供了至少8種查詢方式,詳細內容,請看.NET ORM 的 “SOD蜜”--零基礎入門篇
為了更好的展示SOD框架在專案上的實際應用,下面使用常見的訂單操作來說明物體類定義和ORM查詢更多的細節,
5.1,訂單業務類設計
訂單類一般需要包含用戶資訊、訂單名、訂單價格、商品清單、識訓地址等資訊,為了簡化示例,我們設計一個簡單訂單類SimpleOrder,它僅僅包含訂單的基礎資訊和商品清單,
所以訂單業務類的設計包含一個訂單類和商品清單類,為統一訂單物體類的設計,這里先設計訂單介面型別,
public interface ISimpleOrder
{
long OrderID { get; set; }
string OrderName { get; set; }
int UserID { get; set; }
DateTime OrderDate { get; set; }
double OrderPrice { get; set; }
ISimpleOrderItem[] OrderItems { get; }
}
public interface ISimpleOrderItem
{
string GoodsID { get; set; }
string GoodsName { get; set; }
int Number { get; set; }
double UnitPrice { get; set; }
}
然后設計訂單業務類:
public class SimpleOrder : ISimpleOrder
{
public long OrderID { get; set; }
public string OrderName { get; set; }
public int UserID { get; set; }
public DateTime OrderDate { get; set; }
public double OrderPrice { get; set; }
public ISimpleOrderItem[] OrderItems { get; set; }
}
public class SimpleOrderItem: ISimpleOrderItem
{
public string GoodsID { get; set; }
public string GoodsName { get; set; }
public int Number { get; set; }
public double UnitPrice { get; set; }
}
SimpleOrder包含一個商品清單OrderItems,它是ISimpleOrderItem[]型別,SimpleOrderItem繼承了ISimpleOrderItem介面,SimpleOrder的子型別使用介面型別來定義,方便訂單物體類的子型別能繼承同樣的介面,
5.2,訂單物體類設計
訂單物體類SimpleOrderEntity同SimpleOrder業務類一樣,都繼承了ISimpleOrder介面:
public class SimpleOrderEntity :EntityBase, ISimpleOrder
{
public SimpleOrderEntity()
{
TableName = "TbOrders";
PrimaryKeys.Add(nameof(OrderID));
}
public long OrderID
{
get { return getProperty<long>(nameof(OrderID)); }
set { setProperty(nameof(OrderID), value); }
}
public string OrderName
{
get { return getProperty<string>(nameof(OrderName)); }
set { setProperty(nameof(OrderName), value, 100); } //長度 100
}
public int UserID
{
get { return getProperty<int>(nameof(UserID)); }
set { setProperty(nameof(UserID), value); }
}
public DateTime OrderDate
{
get { return getProperty<DateTime>(nameof(OrderDate)); }
set { setProperty(nameof(OrderDate), value); }
}
public double OrderPrice
{
get { return getProperty<double>(nameof(OrderPrice)); }
set { setProperty(nameof(OrderPrice), value); }
}
public ISimpleOrderItem[] OrderItems
{
get {
return this.OrderItemEntities.ToArray();
}
}
public List<SimpleOrderItemEntity> OrderItemEntities { get; set; }
}
public class SimpleOrderItemEntity : EntityBase, ISimpleOrderItem
{
public SimpleOrderItemEntity()
{
TableName = "TbOrderItems";
IdentityName = nameof(ID);
PrimaryKeys.Add(nameof(ID));
SetForeignKey<SimpleOrderEntity>(nameof(OrderID));
}
public long ID
{
get { return getProperty<long>(nameof(ID)); }
set { setProperty(nameof(ID), value); }
}
public long OrderID
{
get { return getProperty<long>(nameof(OrderID)); }
set { setProperty(nameof(OrderID), value); }
}
public string GoodsID
{
get { return getProperty<string>(nameof(GoodsID)); }
set { setProperty(nameof(GoodsID), value, 100); } //長度 100
}
public string GoodsName
{
get { return getProperty<string>(nameof(GoodsName)); }
set { setProperty(nameof(GoodsName), value, 100); } //長度 100
}
public int Number
{
get { return getProperty<int>(nameof(Number)); }
set { setProperty(nameof(Number), value); }
}
public double UnitPrice
{
get { return getProperty<double>(nameof(UnitPrice)); }
set { setProperty(nameof(UnitPrice), value); }
}
}
在訂單物體類的定義中,使用了nameof陳述句,這樣更改屬性名維持屬性名和資料表欄位名的一致性就很方便了,
另外,在訂單物體類SimpleOrderEntity 多定義了一個屬性OrderItemEntities:
public List
OrderItemEntities
它并不是訂單介面ISimpleOrder要求定義的屬性,設計OrderItemEntities的目的是為了讓框架識別這是一個“子物體類”屬性,在需要到時候可以將子物體類一并查詢出來,
5.3,創建和保存訂單
在實際的專案開發中,為了區分業務層和持久層代碼,需要定義創建訂單和保存訂單的介面,并且需要在業務物件和物體類物件之間進行資料映射,為簡化示例,這里創建和保存訂單都直接操作訂單物體類,
創建一個訂單,它的商品清單包含兩個商品物件:
SimpleOrderEntity order1 = new SimpleOrderEntity();
order1.OrderID = CommonUtil.NewSequenceGUID();
order1.OrderName = "筆記本訂單_某想XL型號_" + DateTime.Now.ToString("yyyyMMdd");
order1.UserID = 1;
order1.OrderDate = DateTime.Now;
order1.OrderPrice = 5000;
var orderItems = new SimpleOrderItemEntity[] {
new SimpleOrderItemEntity()
{
OrderID = order1.OrderID,
GoodsID = "123456_7890_abc",
GoodsName = "某想XL型號",
UnitPrice = 4500,
Number = 1
},
new SimpleOrderItemEntity()
{
OrderID = order1.OrderID,
GoodsID = "1526656_7670_bcd",
GoodsName = "藍牙鍵盤",
UnitPrice = 500,
Number = 1
}
};
然后保存訂單,由于保存訂單實際上需要同時保存訂單清單物體類物件,所以需要使用事務來保存資料:
//自動創建表
SimpleDbContext db2_ctx = new SimpleDbContext();
//使用事務添加物體物件
//插入訂單
bool addResult = db2_ctx.Transaction(ctx =>
{
ctx.Add(order1);
ctx.AddList(orderItems);
}, out string errorMessage);
if (addResult)
Console.WriteLine("保存訂單資訊成功!");
else
Console.WriteLine("保存訂單資訊失敗,原因:{0}", errorMessage);
上面的代碼使用了DbContext物件的事務方法來保存訂單資料,也可以直接使用AdoHelper物件的事務方法來操作,但需要自己處理資料訪問可能發生的例外,
5.4,更新訂單
更新操作可以使用DbContext方式,也可以使用EntityQuery方式,下面以更新訂單的價格作為示例:
//方式一:使用DbContext方式
order1.OrderPrice = 4999;
int ur1= db2_ctx.Update(order1);
if (ur1 > 0)
Console.WriteLine("訂單價格更新成功,點單號:{0},價格:{1}",order1.OrderID,order1.OrderPrice);
//方式二:使用EntityQuery方式
order1.OrderPrice = 4998;
int ur2 = EntityQuery<SimpleOrderEntity>.Instance.Update(order1, db2);
if (ur2 > 0)
Console.WriteLine("訂單價格更新成功,點單號:{0},價格:{1}", order1.OrderID, order1.OrderPrice);
5.4,查詢指定用戶的訂單
查詢指定用戶的訂單,包括訂單中的商品明細:
SimpleOrderEntity order2 = new SimpleOrderEntity() { UserID = 1 };
var oql_order= OQL.From(order2)
.Select()
.Where(order2.UserID)
.END;
var list_order = EntityQuery<SimpleOrderEntity>.QueryListWithChild(oql_order, db2);
上面示例代碼使用了EntityQuery類的QueryListWithChild方法來查詢記錄,注意使用該方法查詢“主-子物體類”的時候,子物體類需要指定外鍵屬性,
例如在SimpleOrderItemEntity的定義中,使用SetForeignKey 方法定義了關聯的主物體類的屬性OrderID:
SetForeignKey
(nameof(OrderID));
其中SimpleOrderEntity型別是SimpleOrderItemEntity的父型別,nameof(OrderID)對應的是SimpleOrderItemEntity型別的屬性欄位OrderID.
5.5,查詢訂單用戶資訊
查詢最近10條購買了筆記本的訂單用戶記錄,包括用戶年齡、性別資料,該查詢需要關聯訂單表和用戶表資料進行聯合查詢:
SimpleOrderEntity soe = new SimpleOrderEntity();
UserEntity ue = new UserEntity();
var oql_OrderUser = OQL.From(soe)
.InnerJoin(ue).On(soe.UserID, ue.ID)
.Select()
.Where(cmp => cmp.Comparer(soe.OrderName, "like", "筆記本訂單%"))
.OrderBy(soe.OrderID,"desc")
.END;
oql_OrderUser.Limit(10);
EntityContainer ec = new EntityContainer(oql_OrderUser, db2);
var listView = ec.MapToList(() => new {
soe.OrderID,
soe.OrderName,
OrderPrice =soe.OrderPrice.ToString("#0.00")+"¥",
UserID =ue.ID,
UserName =ue.Name,
Sex =ue.Sex?"男":"女",
UserAge =DateTime.Now.Year- ue.BirthDate.Year,
soe.OrderDate
});
上面的關聯查詢使用了OQL的InnerJoin查詢,由于關聯查詢一般都需要將結果映射到一個新的結果型別,所以OQL的Select方法對于查詢欄位的選擇延遲到EntityContainer物件的MapToList方法里面進行,
MapToList方法可以將結果映射到一個已知的型別,包括物體型別別,也可以直接映射到一個匿名型別,如上面的示例,
6,資料表單
SOD框架的資料表單開發技識訓于一套資料控制元件介面,實作了WebForm/WinForm/WPF的表單資料系結、資料收集與自動更新技術,實作了表單表單資料的填充、收集、清除,和直接到資料庫的CRUD功能,
框架對于WebForm/WinForm常用的表單控制元件CheckBox、DropDownList、Label、ListBox、RadioButton、TextBox都實作了 IDataControl介面,通過這個介面可以非常方便的進行表單資料開發,在WebForm/WinForm下有一致的開發體驗,
有關SOD框架的資料表單開發技術,請看作者下面兩篇博客文章:
- 不使用反射,“一行代碼”實作Web、WinForm表單表單資料的填充、收集、清除,和到資料庫的CRUD
- “老壇泡新菜”:SOD MVVM框架,讓WinForms煥發新春
更多詳細的內容介紹,請看框架作者的圖書《SOD框架“企業級”應用資料架構實戰 》書中 【第五章 資料表單開發】 的內容,
三、其它
Thank you for your donation
歡迎您捐助本專案,捐贈地址:框架官網
頁面編輯時間:2023.7.9
《SOD框架“企業級”應用資料架構實戰》轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/557145.html
標籤:其他
上一篇:集成測驗最全詳解,看完必須懂了
下一篇:返回列表