写点什么

Fast.Framework ORM 于中秋节后 正式开源

作者:Java-fenn
  • 2022 年 9 月 12 日
    湖南
  • 本文字数:7525 字

    阅读完需:约 25 分钟

一、前言

Fast Framework 是一个基于 NET6.0 封装的轻量级 ORM 框架 支持多种数据库 SqlServer Oracle MySql PostgreSql Sqlite

优点: 体积小、可动态切换不同实现类库、原生支持微软特性、流畅 API、使用简单、性能高、模型数据绑定采用 委托+缓存、强大的表达式解析、子查询的原生支持、复杂表达式含成员变量解析,解析性能是目前常见框架中 No1 主要是有缓存的支持 、源代码可读性强。

缺点:目前仅支持 Db Frist Code Frist 暂时不考虑 主要是需要花费大量时间和精力。

二、项目明细

三、核心对象

  • 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方法
复制代码
  • 查询并插入 仅支持同实例的数据库 跨库 个人还是建议 用事务分开写查询和插入

//方式1var 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 查询

// 方式1var data1 = await db.Query<Product>().Where(w => SqlFunc.In(w.ProductCode, "1001", "1002")).ToListAsync();
// 方式2var 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}");}}};
复制代码

十、事务

  • 普通事务

try{await db.Ado.BeginTranAsync();//开启事务
// 执行 CRUD
await db.Ado.CommitTranAsync();//提交事务}catch (Exception ex){Console.WriteLine(ex.Message);await db.Ado.RollbackTranAsync();//回滚事务}
复制代码
  • 更大范围的事务 使用微软 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 DictionaryListBuildAsyncvar data = await db.Ado.ExecuteReaderAsync(CommandType.Text, "select * from product", null).ListBuildAsync<Product>();
复制代码


用户头像

Java-fenn

关注

需要Java资料或者咨询可加我v : Jimbye 2022.08.16 加入

还未添加个人简介

评论

发布
暂无评论
Fast.Framework ORM 于中秋节后 正式开源_Java_Java-fenn_InfoQ写作社区