Fast Framework 作者 Mr-zhong 開源項目地址 https://github.com/China-Mr-zhong/Fast.Framework QQ交流群 954866406 歡迎小伙伴加入交流探討技術 一、前言 Fast Framework 是一個基於NET6.0 封裝的輕量 ...
Fast Framework
作者 Mr-zhong
開源項目地址 https://github.com/China-Mr-zhong/Fast.Framework
QQ交流群 954866406 歡迎小伙伴加入交流探討技術
一、前言
Fast Framework 是一個基於NET6.0 封裝的輕量級 ORM 框架 支持多種資料庫 SqlServer Oracle MySql PostgreSql Sqlite
優點: 體積小、可動態切換不同實現類庫、原生支持微軟特性、流暢API、使用簡單、性能高、模型數據綁定採用 委托+緩存、強大的表達式解析、子查詢的原生支持、複雜表達式含成員變數解析,解析性能是目前常見框架中 No1 主要是有緩存的支持 、源代碼可讀性強。
缺點:目前僅支持Db Frist Code Frist 暫時不考慮 主要是需要花費大量時間和精力。
二、項目明細
名稱 | 說明 |
---|---|
Fast.Framework | 介面實現類庫(框架核心介面實現) |
Fast.Framework.Aop | Aop類庫(基於微軟DispatchProxy抽象類封裝) |
Fast.Framework.Extensions | 擴展類庫(主要擴展框架核心方法,方便使用) |
Fast.Framework.Interfaces | 介面類庫(框架核心介面定義) |
Fast.Framework.Logging | 日誌類庫(主要實現自定義文件日誌) |
Fast.Framework.Models | 模型 框架所用到的實體類 |
Fast.Framework.Utils | 工具類庫 |
Fast.Framework.Test | 控制台終端測試項目 |
Fast.Framework.UnitTest | 單元測試項目 |
Fast.Framework.Web.Test | Web測試項目 |
三、核心對象
-
Ado 原生Ado對象
IAdo ado = new AdoProvider(new DbOptions() { DbId = "1", DbType = DbType.MySQL, ProviderName = "MySqlConnector", FactoryName = "MySqlConnector.MySqlConnectorFactory,MySqlConnector", ConnectionStrings = "server=localhost;database=Test;user=root;pwd=123456789;port=3306;min pool size=3;max pool size=100;connect timeout=30;" });
-
DbContext 支持多租戶 支持切換不同Ado實現類庫 設置 ProviderName和FactoryName 即可
IDbContext db = new DbContext(new List<DbOptions>() { new DbOptions() { DbId = "1", DbType = DbType.MySQL, ProviderName = "MySqlConnector", FactoryName = "MySqlConnector.MySqlConnectorFactory,MySqlConnector", ConnectionStrings = "server=localhost;database=Test;user=root;pwd=123456789;port=3306;min pool size=3;max pool size=100;connect timeout=30;" }});
依賴註入
// 註冊服務
builder.Services.AddScoped<IDbContext, DbContext>();
// 資料庫選項支持Options介面註入 不是很理解的可以看代碼實現
builder.Services.Configure<List<DbOptions>>(configuration.GetSection("DbConfig"));
// 產品服務類 通過構造方法註入
public class ProductService
{
/// <summary>
/// 資料庫
/// </summary>
private readonly IDbContext db;
/// <summary>
/// 構造方法
/// </summary>
/// <param name="db">資料庫</param>
public ProductService(IDbContext db)
{
this.db = db;
}
}
四、插入
-
實體對象插入
var product = new Product() { ProductCode = "1001", ProductName = "測試商品1" }; var result = await db.Insert(product).ExceuteAsync(); Console.WriteLine($"實體對象插入 受影響行數 {result}");
-
實體對象插入並返回自增ID 僅支持 SQLServer MySQL SQLite
var product = new Product() { ProductCode = "1001", ProductName = "測試產品1" }; var result = await db.Insert(product).ExceuteReturnIdentityAsync(); Console.WriteLine($"實體對象插入 返回自增ID {result}");
-
實體對象列表插入
var list = new List<Product>(); for (int i = 0; i < 2100; i++) { list.Add(new Product() { ProductCode = $"編號{i + 1}", ProductName = $"名稱{i + 1}" }); } var result = await db.Insert(list).ExceuteAsync(); Console.WriteLine($"實體對象列表插入 受影響行數 {result}");
-
匿名對象插入
var obj = new { ProductCode = "1001", ProductName = "測試商品1" }; //註意:需要使用As方法顯示指定表名稱 var result = await db.Insert(obj).As("product").ExceuteAsync(); Console.WriteLine($"匿名對象插入 受影響行數 {result}");
-
匿名對象列表插入
var list = new List<object>(); for (int i = 0; i < 2100; i++) { list.Add(new { ProductCode = $"編號{i + 1}", ProductName = $"名稱{i + 1}" }); } //註意:需要使用As方法顯示指定表名稱 var result = await db.Insert(list).As("Product").ExceuteAsync(); Console.WriteLine($"匿名對象列表插入 受影響行數 {result}");
-
字典插入
var product = new Dictionary<string, object>() { {"ProductCode","1001"}, { "ProductName","測試商品1"} }; //註意:需要顯示指定類型否則無法重載到正確的方法,如果沒有實體類型可用object類型並配合As方法顯示指定表名稱. var result = await db.Insert<Product>(product).ExceuteAsync(); Console.WriteLine($"字典插入 受影響行數 {result}");
-
字典列表插入
var list = new List<Dictionary<string, object>>(); for (int i = 0; i < 2100; i++) { list.Add(new Dictionary<string, object>() { {"ProductCode","1001"}, { "ProductName","測試商品1"} }); } //註意:需要顯示指定泛型類型否則無法重載到正確的方法,如果沒有實體可用object類型並配合As方法顯示指定表名稱. var result = await db.Insert<Product>(list).ExceuteAsync(); Console.WriteLine($"字典列表插入 受影響行數 {result}");
五、刪除
-
實體對象刪除
var product = new Product() { ProductId = 1, ProductCode = "1001", ProductName = "測試商品1" }; //註意:必須標記KeyAuttribute特性 否則將拋出異常 var result = await db.Delete(product).ExceuteAsync(); Console.WriteLine($"實體刪除 受影響行數 {result}");
-
無條件刪除
var result = await db.Delete<Product>().ExceuteAsync(); Console.WriteLine($"無條件刪除 受影響行數 {result}");
-
表達式刪除
var result = await db.Delete<Product>().Where(w => w.ProductId == 1).ExceuteAsync(); Console.WriteLine($"條件刪除 受影響行數 {result}");
-
特殊刪除
//特殊用法 如需單個條件或多個可搭配 WhereColumn或WhereColumns方法 var result = await db.Delete<object>().As("Product").ExceuteAsync(); Console.WriteLine($"無實體刪除 受影響行數 {result}");
六、更新
-
實體對象更新
var product = new Product() { ProductId = 1, ProductCode = "1001", ProductName = "測試商品1" }; //註意:標記KeyAuttribute特性屬性或使用Where條件,為了安全起見全表更新將必須使用Where方法 var result = await db.Update(product).ExceuteAsync(); Console.WriteLine($"對象更新 受影響行數 {result}");
-
指定列更新
var result = await db.Update<Product>(new Product() { ProductCode = "1001", ProductName = "1002" }) .Columns("ProductCode", "ProductName").ExceuteAsync(); // 欄位很多的話可以直接new List<string>(){"列1","列2"}
-
忽略列更新
var result = await db.Update<Product>(new Product() { ProductCode = "1001", ProductName = "1002" }) .IgnoreColumns("Custom1").ExceuteAsync(); // 同上使用方法一樣
-
實體對象列表更新
var list = new List<Product>(); for (int i = 0; i < 2022; i++) { list.Add(new Product() { ProductCode = $"編號{i + 1}", ProductName = $"名稱{i + 1}" }); } //註意:標記KeyAuttribute特性屬性或使用WhereColumns方法指定更新條件列 var result = await db.Update(list).ExceuteAsync(); Console.WriteLine($"對象列表更新 受影響行數 {result}");
-
匿名對象更新
var obj = new { ProductId = 1, ProductCode = "1001", ProductName = "測試商品1" }; //註意:需要顯示指定表名稱 以及更新條件 使用 Where或者WhereColumns方法均可 var result = await db.Update(obj).As("product").WhereColumns("ProductId").ExceuteAsync(); Console.WriteLine($"匿名對象更新 受影響行數 {result}");
-
匿名對象列表更新
var list = new List<object>(); for (int i = 0; i < 2022; i++) { list.Add(new { ProductId = i + 1, ProductCode = $"編號{i + 1}", ProductName = $"名稱{i + 1}" }); } //由於是匿名對象需要顯示指定表名稱,使用WhereColumns方法指定更新條件列 var result = await db.Update(list).As("Product").WhereColumns("ProductId").ExceuteAsync(); Console.WriteLine($"匿名對象列表更新 受影響行數 {result}");
-
字典更新
var product = new Dictionary<string, object>() { { "ProductId",1}, {"ProductCode","1001"}, { "ProductName","測試商品1"} }; //註意:需要顯示指定泛型類型否則無法重載到正確的方法並且使用WhereColumns方法指定條件列 var result = await db.Update<Product>(product).WhereColumns("ProductId").ExceuteAsync(); Console.WriteLine($"字典更新 受影響行數 {result}");
-
字典列表更新
var list = new List<Dictionary<string, object>>(); for (int i = 0; i < 2022; i++) { list.Add(new Dictionary<string, object>() { { "ProductId",i+1}, {"ProductCode",$"更新編號:{i+1}"}, { "ProductName",$"更新商品:{i + 1}"} }); } //註意:需要顯示指定泛型類型否則無法重載到正確的方法並且使用WhereColumns方法執行條件列 var result = await db.Update<Product>(list).WhereColumns("ProductId").ExceuteAsync(); Console.WriteLine($"字典列表更新 受影響行數 {result}");
-
表達式更新
var product = new Product() { ProductId = 1, ProductCode = "1001", ProductName = "測試商品1" }; var result = await db.Update(product).Where(p => p.ProductId == 100).ExceuteAsync(); Console.WriteLine($"表達式更新 受影響行數 {result}");
七、查詢
- 單一查詢
var data = await db.Query<Product>().FristAsync();
-
列表查詢
var data = await db.Query<Product>().ToListAsync();
-
返回單個字典
var data = await db.Query<Product>().ToDictionaryAsync();
-
返回字典列表
var data = await db.Query<Product>().ToDictionaryListAsync();
-
分頁查詢
var page = new Pagination() { Page = 1, PageSize = 100 }; var data = await db.Query<Product>().ToPageListAsync(page);
-
計數查詢
var data = await db.Query<Product>().CountAsync();
-
任何查詢
var data = await db.Query<Product>().AnyAsync();
-
條件查詢
var data = await db.Query<Product>().Where(w => w.ProductId == 1); //需要調用返回數據結果的方法 例如:ToListAsync
-
Like 查詢
var data = await db.Query<Product>().Where(w => w.ProductName.StartsWith("左模糊") || w.ProductName.EndsWith("右模糊") || w.ProductName.Contains("全模糊"));
-
Not Like查詢
var data = await db.Query<Product>().Where(w => !w.ProductName.StartsWith("左模糊") || !w.ProductName.EndsWith("右模糊") || !w.ProductName.Contains("全模糊")); //由於沒有專門去擴展 Not Like 方法,可以用取反或使用比較變通實現 例如 w.ProductName.StartsWith("左模糊")==false //Mysql舉例 最終解析後的結果為 `ProductName` Like '%左模糊' = 0 這種用法資料庫是支持的 相當於 Not Like
-
Select查詢 (選擇欄位)
var data = await db.Query<Product>().Select(s => new { s.ProductId, s.ProductName }).ToListAsync();
-
分組查詢
var data = await db.Query<Product>().GroupBy(s => new { s.ProductId, s.ProductName }).ToListAsync();
-
分組聚合查詢
var sql = db.Query<Order>().InnerJoin<OrderDetail>((a, b) => a.OrderId == b.OrderId).GroupBy((a, b) => new { a.OrderCode }).Select((a, b) => new { a.OrderCode, Sum_Qty = SqlFunc.Sum(b.Qty)//支持嵌套 }).ToListAsync();
-
排序查詢
var data = await db.Query<Product>().OrderBy(s => new { s.CreateTime }).ToListAsync(); //這是多個欄位排序使用方法 還有其它重載方法
-
Having查詢
var data = await db.Query<Product>().GroupBy(s => new { s.ProductId, s.ProductName }).Having(s => SqlFunc.Count(s.ProductId) > 1).ToListAsync(); //必須先使用GroupBy方法 懂得都懂
-
聯表查詢
var data = await db.Query<Product>(). LeftJoin<Class1>((a, b) => a.ProductId == b.ProductId).ToListAsync(); // 右連接對應的是 RightJoin 內連接對應 InnerJoin
-
聯合查詢
var query1 = db.Query<Product>(); var query2 = db.Query<Product>(); db.Union(query1, query2);//聯合 db.UnionAll(query1, query2);//全聯合 //執行查詢調用Toxx方法
-
查詢並插入 僅支持同實例的資料庫 跨庫 個人還是建議 用事務分開寫查詢和插入
//方式1 var result1 = await db.Query<Product>().Where(w => w.ProductId == 1489087).Select(s => new { s.ProductCode, s.ProductName }).Insert<Product>(p => new { p.ProductCode, p.ProductName }); //方式2 需要註意的是 顯示指定不帶 列標識符 例如 `列名稱1` 如有欄位衝突 可自行加上標識符 var result2 = await db.Query<Product>().Where(w => w.ProductId == 1489087).Select(s => new { s.ProductCode, s.ProductName }).Insert("表名稱 同實例不同庫 可以使用 db.資料庫名稱.表名稱 ", "列名稱1", "列名稱2", "`帶標識的列名稱3`"); //方式3 需要註意同方式2 一樣 var result3 = await db.Query<Product>().Where(w => w.ProductId == 1489087).Select(s => new { s.ProductCode, s.ProductName }).Insert("表名稱 同實例不同庫 可以使用 db.資料庫名稱.表名稱 ", new List<string>() { "列名稱1" });
-
In查詢
// 方式1 var data1 = await db.Query<Product>().Where(w => SqlFunc.In(w.ProductCode, "1001", "1002")).ToListAsync(); // 方式2 var data2 = await db.Query<Product>().Where(w => SqlFunc.In(w.ProductCode, new List<string>() { "123", "456" })).ToListAsync(); // 方式3 需要動態更新IN值 使用這種 var list = new List<string>() { "123", "456" }; var data3 = await db.Query<Product>().Where(w => SqlFunc.In(w.ProductCode, list)).ToListAsync(); // 方法4 參數同上一樣 單獨分離IN和NotIN 是為了相容匿名查詢 var data4 = await db.Query<Product>().In("欄位名稱", "1001", "1002").ToListAsync();
-
子查詢
var subQuery = db.Query<Product>().Where(w => w.ProductId == 1).Select(s => s.ProductName); var sql1 = db.Query<Product>().Select(s => new Product() { Custom1 = db.SubQuery<string>(subQuery)// SubQuery 的泛型是根據你左邊賦值的屬性類型來定義 }).ToListAsync(); // 這種沒有使用new 的 泛型可隨意定義 實際作用就是避免 對象屬性賦值類型衝突的問題 var sql2 = db.Query<Product>().Select(s => db.SubQuery<string>(subQuery)).ToListAsync();
八、Lambda表達式
-
高性能表達式動態緩存的支持
var list = new List<string>() { "1001" }; Expression<Func<Product, bool>> ex = p => SqlFunc.In(p.ProductCode, list); for (int i = 1; i <= 3; i++) { list.Add($"動態添加參數{i}"); var stopwatch1 = new Stopwatch(); stopwatch1.Start(); var result = ex.ResolveSql(new ResolveSqlOptions() { DbType = Models.DbType.MySQL, ResolveSqlType = ResolveSqlType.Where }); stopwatch1.Stop(); Console.WriteLine($"解析耗時:{stopwatch1.ElapsedMilliseconds}ms {stopwatch1.ElapsedMilliseconds / 1000.00}s 解析Sql字元串:{result.SqlString}"); }
-
解析結果
解析耗時:14ms 0.014s 解析Sql字元串:p.`ProductCode` IN ( @2dac7a1c4aa64036aeee858b86fbd3a4_0,@2dac7a1c4aa64036aeee858b86fbd3a4_1 ) 解析耗時:0ms 0s 解析Sql字元串:p.`ProductCode` IN ( @3b6b8fcb2f674cf490d44f97525c3c2b_0,@3b6b8fcb2f674cf490d44f97525c3c2b_1,@3b6b8fcb2f674cf490d44f97525c3c2b_2 ) 解析耗時:0ms 0s 解析Sql字元串:p.`ProductCode` IN ( @4447c5d65e8a49c9b04549b7aac868b2_0,@4447c5d65e8a49c9b04549b7aac868b2_1,@4447c5d65e8a49c9b04549b7aac868b2_2,@4447c5d65e8a49c9b04549b7aac868b2_3 )
-
-
動態表達式
var ex = DynamicWhereExp.Create<Product>().AndIF(1 == 1, a => a.DeleteMark == true).Build(); var data =await db.Query<Product>().Where(ex).ToListAsync();
九、資料庫日誌
db.Aop.DbLog = (sql, dp) =>
{
Console.WriteLine($"執行Sql:{sql}");
if (dp != null)
{
foreach (var item in dp)
{
Console.WriteLine($"參數名稱:{item.ParameterName} 參數值:{item.Value}");
}
}
};
十、事務
-
普通事務
await db.Ado.BeginTranAsync();//開啟事務 await db.Ado.ExecuteNonQueryAsync(CommandType.Text, "執行語句1"); await db.Ado.ExecuteNonQueryAsync(CommandType.Text, "執行語句2"); await db.Ado.CommitTranAsync();//提交事務
-
更大範圍的事務 使用微軟 TransactionScope 對象
using (var tran = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled)) { // 執行你的增刪改查 // 可使用原生Ado或DbContext對象的CURD方法 tran.Complete();//提交事務 }
十一、多租戶
-
改變資料庫
//資料庫配置可從Json配置文件載入 IDbContext db = new DbContext(new List<DbOptions>() { new DbOptions() { DbId = "0", DbType = Models.DbType.SQLServer, ProviderName = "System.Data.SqlClient", FactoryName = "System.Data.SqlClient.SqlClientFactory,System.Data", ConnectionStrings = "server=localhost;database=Test;user=sa;pwd=123456789;min pool size=3;max pool size=100;connect timeout=30;" }, new DbOptions() { DbId = "1", DbType = Models.DbType.MySQL, ProviderName = "MySqlConnector", FactoryName = "MySqlConnector.MySqlConnectorFactory,MySqlConnector", ConnectionStrings = "server=localhost;database=Test;user=root;pwd=123456789;port=3306;min pool size=3;max pool size=100;connect timeout=30;" }}); db.ChangeDb("1");//切換到MySQL
十二、原生特性支持
/// <summary>
/// 產品
/// </summary>
[Table("ProductMain")]
public class Product
{
/// <summary>
/// 產品ID
/// </summary>
[Key]
public int ProductId { get; set; }
/// <summary>
/// 產品編號
/// </summary>
[Column("ProductCode")]//不標記預設取當前屬性名稱
public string ProductCode { get; set; }
/// <summary>
/// 自定義1
/// </summary>
[NotMapped]
public string Custom1 { get; set; }
}
十三、原生Ado使用
// 原始起步
// var conn = db.Ado.DbProviderFactory.CreateConnection();
// var cmd = conn.CreateCommand();
// 封裝的方法分別以Execute和Create開頭以及預處理 PrepareCommand 方法
// 該方法可以自動幫你處理執行的預操作,主要作用是代碼復用。
// 當有非常複雜的查詢 ORM不能滿足需求的時候可以使用原生Ado滿足業務需求
// 構建數據集核心擴展方法 分別有 FristBuildAsync ListBuildAsync DictionaryBuildAsync DictionaryListBuildAsync
var data = await db.Ado.ExecuteReaderAsync(CommandType.Text, "select * from product", null).ListBuildAsync<Product>();