自研、好用、够快、稳定、代码可读性强的ORM
Fast Framework
作者 Mr-zhong
代码改变世界....
一、前言
Fast Framework 基于NET6.0 封装的轻量级 ORM 框架 支持多种数据库 SqlServer Oracle MySql PostgreSql Sqlite
优点: 体积小、可动态切换不同实现类库、原生支持微软特性、流畅API、使用简单、性能高、模型数据绑定采用Emit、强大的表达式解析、支持多种子查询可实现较为复杂查询、源代码可读性强。
缺点:目前仅支持Db Frist Code Frist 后续迭代。
开源地址:https://github.com/China-Mr-zhong/Fast-Framework (唯一)
多个生产项目稳定运行中
二、项目明细
名称 | 说明 |
---|---|
Fast.Framework | ORM |
Fast.Framework.Logging | 文件日志 (扩展项目可不使用) |
Fast.Framework.DependencyInjection | 依赖注入 (扩展项目可不使用) |
Fast.Framework.Test | 控制台测试项目 |
Fast.Framework.UnitTest | 单元测试项目 |
Fast.Framework.Web.Test | Web测试项目 |
三、核心对象
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;"
}});
DbOptions Json文件配置格式
"DbOptions": [
{
"DbId": 1,
"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=120;"
}]
主从分离(读写分离)配置
"DbOptions": [
{
"DbId": 2,
"DbType": "MySQL",
"IsDefault": true,
"ProviderName": "MySqlConnector",
"FactoryName": "MySqlConnector.MySqlConnectorFactory,MySqlConnector",
"ConnectionStrings": "server=localhost;database=Test;user=root;pwd=123456789;port=3306;min pool size=0;max pool size=100;connect timeout=120;AllowLoadLocalInfile=true;",
"UseMasterSlaveSeparation": true,//使用主从分离 注意所有事务将强制走主库
"SlaveItems": [
{
"Weight": 1,//注意多个从库 必须配置权重且总权重>从库数
"ConnectionStrings": "server=localhost;database=Test1;user=root;pwd=123456789;port=3306;min pool size=0;max pool size=100;connect timeout=120;AllowLoadLocalInfile=true;",
"Description": "从库连接配置"
},
{
"Weight": 2,
"ConnectionStrings": "server=localhost;database=Test2;user=root;pwd=123456789;port=3306;min pool size=0;max pool size=100;connect timeout=120;AllowLoadLocalInfile=true;",
"Description": "从库连接配置"
}
],
"Description": "主库连接配置"
}
]
Asp.net Core 依赖注入
// 注册服务
var builder = WebApplication.CreateBuilder(args); // 添加数据库上下文
builder.Services.AddFastDbContext(); // 从Json配置文件加载数据库选项
builder.Services.Configure<List<DbOptions>>(builder.Configuration.GetSection("DbOptions")); // 产品服务类 通过构造方法注入
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 = db.Insert(product).Exceute();
实体对象插入并返回自增ID 仅支持 SQLServer MySQL SQLite
var product = new Product()
{
ProductCode = "1001",
ProductName = "测试产品1"
};
var result = db.Insert(product).ExceuteReturnIdentity();
实体对象列表插入
var list = new List<Product>();
for (int i = 0; i < 2100; i++)
{
list.Add(new Product()
{
ProductCode = $"编号{i + 1}",
ProductName = $"名称{i + 1}"
});
}
var result = db.Insert(list).Exceute();
匿名对象插入
var obj = new
{
ProductCode = "1001",
ProductName = "测试商品1"
};
//注意:需要使用As方法显示指定表名称
var result = db.Insert(obj).As("Product").Exceute();
匿名对象列表插入
var list = new List<object>();
for (int i = 0; i < 2100; i++)
{
list.Add(new
{
ProductCode = $"编号{i + 1}",
ProductName = $"名称{i + 1}"
});
}
//注意:需要使用As方法显示指定表名称
var result = db.Insert(list).As("Product").Exceute();
字典插入
var product = new Dictionary<string, object>()
{
{"ProductCode","1001"},
{ "ProductName","测试商品1"}
};
var result = db.Insert(product).As("Product").Exceute();
字典列表插入
var list = new List<Dictionary<string, object>>();
for (int i = 0; i < 2100; i++)
{
list.Add(new Dictionary<string, object>()
{
{"ProductCode","1001"},
{ "ProductName","测试商品1"}
});
}
var result = db.Insert(list).As("Product").Exceute();
五、删除
实体对象删除
var product = new Product()
{
ProductId = 1,
ProductCode = "1001",
ProductName = "测试商品1"
};
var result = db.Delete(product).Exceute();
实体对象列表删除
var list = new List<Product>();
for (int i = 0; i < 2100; i++)
{
list.Add(new Product()
{
ProductCode = $"编号{i + 1}",
ProductName = $"名称{i + 1}"
});
}
var result = db.Delete(list).Exceute();
无条件删除
var result = db.Delete<Product>().Exceute();
条件删除
var result = await db.Delete<Product>().Where(w => w.ProductId == 1).ExceuteAsync();
逻辑删除
/*
可用特性标记逻辑删除列 仅支持 int bool datetime 类型,其它类型不合适 /// <summary>
/// 删除标记
/// </summary>
[Logic]
public bool DeleteMark { get; set; } */ //额外设置其它属性值,使用SetColumns方法前需先使用IsLogic方法
//类型逻辑删除
var result1 = db.Delete<Product>().IsLogic().SetColumns(c => new Product()
{
ModifyTime = DateTime.Now
}).Where(w => w.ProductId == 1).Exceute(); //对象逻辑删除
var result2 = db.Delete(new Product() { ProductId = 1 }).IsLogic().SetColumns(c => new Product()
{
ModifyTime = DateTime.Now
}).Exceute(); //特殊逻辑删除(不想建实体类可以用该方式)
var result3 = db.Delete<object>().As("Product").IsLogic().SetColumns(c => new
{
ModifyTime = DateTime.Now
}).Exceute();
特殊删除
//特殊用法 如需单个条件或多个可搭配 Where或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 = db.Update(product).Exceute();
指定列更新
var result = db.Update<Product>(new Product() { ProductCode = "1001", ProductName = "1002" }).Columns(c=> new { c.ProductCode , c.ProductName }).Exceute();
// 推荐使用表达式 c=>new {} 好处更改属性名称可以同步修改
忽略列更新
var result = db.Update<Product>(new Product() { ProductCode = "1001", ProductName = "1002" }).IgnoreColumns(c=> new { c.Custom1 }).Exceute();
// 同上使用方法一样
实体对象列表更新
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 = db.Update(list).Exceute();
匿名对象更新
var obj = new
{
ProductId = 1,
ProductCode = "1001",
ProductName = "测试商品1"
};
//注意:需要显示指定表名称 以及更新条件 使用 Where或者WhereColumns方法均可
var result = db.Update(obj).As("product").WhereColumns("ProductId").Exceute();
匿名对象列表更新
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 = db.Update(list).As("Product").WhereColumns("ProductId").Exceute();
字典更新
var product = new Dictionary<string, object>()
{
{ "ProductId",1},
{"ProductCode","1001"},
{ "ProductName","测试商品1"}
};
var result = db.Update(product).As("Product").WhereColumns("ProductId").Exceute();
字典列表更新
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}"}
});
}
var result = db.Update(list).As("Product").WhereColumns("ProductId").Exceute();
设置列更新
// 设置列更新
db.Update<Product>().SetColumns(c => new Product()
{
ProductCode = "1001",
ProductName = "测试产品1"
}).Where(w => w.ProductId == 1).Exceute(); // 对象组合设置列更新
var product = new Product()
{
ProductId = 1,
ProductCode = "1001",
ProductName = "测试商品1"
};
var result = db.Update(product).SetColumns(c => new Product()
{
Custom1 = "测试"
}).Exceute();
指定条件更新
var product = new Product()
{
ProductId = 1,
ProductCode = "1001",
ProductName = "测试商品1"
};
var result = db.Update(product).Where(p => p.ProductId == 100).Exceute();
Console.WriteLine($"表达式更新 受影响行数 {result}");
并发更新 乐观锁-版本控制
//注意:仅支持非列表更新 版本列数据类型仅支持 object、string、Guid 时间类型存在精度丢失所以不做支持
var obj = db.Query<Product>().Where(w => w.ProductId == 1).Frist();
obj.Custom1 = "测试版本控制修改";
//参数为 true 更新失败将抛出异常
var result = db.Update(obj).ExceuteWithOptLock(true);
七、查询
单一查询
var data = db.Query<Product>().First();
列表查询
var data = db.Query<Product>().ToList();
返回单个字典
var data = db.Query<Product>().ToDictionary();
返回字典列表
var data = db.Query<Product>().ToDictionaryList();
分页查询
//分页查询不返回总数
var data = db.Query<Product>().ToPageList(1,100);
//分页查询返回总数
var total = 0;//定义总数变量
var data = db.Query<Product>().ToPageList(1, 1, ref total);
Console.WriteLine($"总数:{total}");
计数查询
var data = db.Query<Product>().Count();
任何查询
var data = db.Query<Product>().Any();
条件查询
var data = db.Query<Product>().Where(w => w.ProductId == 1).ToList;
Like 查询
var data = db.Query<Product>().Where(w => w.ProductName.StartsWith("左模糊") || w.ProductName.EndsWith("右模糊") || w.ProductName.Contains("全模糊")).ToList();
Not Like查询
var data = db.Query<Product>().Where(w => !w.ProductName.StartsWith("左模糊") || !w.ProductName.EndsWith("右模糊") || !w.ProductName.Contains("全模糊")).ToList();
Select查询 (选择字段)
var data = db.Query<Product>().Select(s => new
{
s.ProductId,
s.ProductName
}).ToList();
Select查询 (Case When)
var data = db.Query<Product>().Select(s => new
{
CaseTest1 = SqlFunc.Case(s.Custom1).When("1").Then("xx1").When("2").Then("xx2").Else("xx3").End(),
CaseTest2 = SqlFunc.CaseWhen<string>(s.Custom1 == "1").Then("xx1").When(s.Custom1 == "2").Then("xx2").Else("xx3").End()
}).ToList();
分组查询
var data = db.Query<Product>().GroupBy(s => new
{
s.ProductId,
s.ProductName
}).ToList();
分组聚合查询
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)//支持嵌套
}).ToList();
排序查询
var data = db.Query<Product>().OrderBy(s => new
{
s.CreateTime
}).ToList();
//这是多个字段排序使用方法 还有其它重载方法
Having查询
var data = db.Query<Product>().GroupBy(s => new
{
s.ProductId,
s.ProductName
}).Having(s => SqlFunc.Count(s.ProductId) > 1).ToList();
//必须先使用GroupBy方法 懂得都懂
联表查询
var data = db.Query<Product>().LeftJoin<Class1>((a, b) => a.ProductId == b.ProductId).ToList();
// 右连接 RightJoin 内连接 InnerJoin 全连接 FullJoin
联合查询
var query1 = db.Query<Product>();
var query2 = db.Query<Product>();
db.Union(query1, query2);//联合
db.UnionAll(query1, query2);//全联合
//执行查询调用Toxx方法
导航查询 (支持无限层级)
/// <summary>
/// 类别
/// </summary>
public class Category
{
/// <summary>
/// 类别ID
/// </summary>
[Key]
public int CategoryId { get; set; } /// <summary>
/// 类别名称
/// </summary>
public string CategoryName { get; set; } /// <summary>
/// 产品 Navigate MainName和ChildName 可不显示指定,会自动查找主键匹配或ID为结尾的属性
/// </summary>
[Navigate(MainName = nameof(CategoryId), ChildName = nameof(Product.CategoryId))]
public IEnumerable<Product> Products { get; set; } } var data = db.Query<Category>()
.Include(i => i.Products)
.ToList();
查询并插入 仅支持同实例的数据库 跨库 个人还是建议 用事务分开写查询和插入
//方式1
var result1 = db.Query<Product>().Where(w => w.ProductId == 1489087).Select(s => new
{
s.ProductCode,
s.ProductName
}).Insert<Product>(p => new
{
p.ProductCode,
p.ProductName
}); //方式2
var result2 = db.Query<Product>().Where(w => w.ProductId == 1489087).Select(s => new
{
s.ProductCode,
s.ProductName
}).Insert("表名称 同实例不同库 可以使用 db.数据库名称.表名称 ", "列名称1", "列名称2"); //方式3 需要注意同方式2 一样
var result3 = db.Query<Product>().Where(w => w.ProductId == 1489087).Select(s => new
{
s.ProductCode,
s.ProductName
}).Insert("表名称 同实例不同库 可以使用 db.数据库名称.表名称 ", new List<string>() { "列名称1" });
In查询
var data1 = db.Query<Product>().Where(w => new List<string>(){"1001", "1002"}.Contains(w.ProductCode)).ToList();
Select嵌套查询和子查询
var data1 = db.Query<Product>().Select(s => new
{
XX = db.Query<Product>().Select(s => 1).First()//需调用返回结果的方法 否则无法解析
}).First(); //进价用法,下面示例方法的重载均支持
var count = 0;
var refAsync = new RefAsync<int>(); var query = db.Query<Product>().Select(s => new
{
WithAttr_First = db.QueryWithAttr<Product>().First(),
WithAttr_FirstAsync = db.QueryWithAttr<Product>().FirstAsync(),
WithAttr_ToList = db.QueryWithAttr<Product>().ToList(),
WithAttr_ToListAsync = db.QueryWithAttr<Product>().ToListAsync(),
First_1 = db.Query<Category>().Select(s => 1).First(),//解析成Sql
First = db.Query<Category>().First(),
FirstAsync = db.Query<Category>().FirstAsync(),
ToArray = db.Query<Category>().ToArray(),
ToArrayAsync = db.Query<Category>().ToArrayAsync(),
ToList = db.Query<Category>().ToList(),
ToListAsync = db.Query<Category>().ToListAsync(),
ToPageList = db.Query<Category>().ToPageList(1, 10),
ToPageListAsync = db.Query<Category>().ToPageListAsync(1, 10),
ToPageList_Count = db.Query<Category>().ToPageList(1, 10, ref count),
ToPageListAsync_Count = db.Query<Category>().ToPageListAsync(1, 10, refAsync),
ToDictionary = db.Query<Category>().ToDictionary(),
ToDictionaryAsync = db.Query<Category>().ToDictionaryAsync(),
ToDictionaryList = db.Query<Category>().ToDictionaryList(),
ToDictionaryListAsync = db.Query<Category>().ToDictionaryListAsync(),
ToDictionaryPageList = db.Query<Category>().ToDictionaryPageList(1, 10),
ToDictionaryPageListAsync = db.Query<Category>().ToDictionaryPageListAsync(1, 10),
ToDictionaryPageList_Count = db.Query<Category>().ToDictionaryPageList(1, 10, ref count),
ToDictionaryPageListAsync_Count = db.Query<Category>().ToDictionaryPageListAsync(1, 10, refAsync),
ToDataTable = db.Query<Category>().ToDataTable(),
ToDataTableAsync = db.Query<Category>().ToDataTableAsync(),
ObjToJson = db.Query<Category>().ObjToJson(),
ObjToJsonAsync = db.Query<Category>().ObjToJsonAsync(),
ObjListToJson = db.Query<Category>().ObjListToJson(),
ObjListToJsonAsync = db.Query<Category>().ObjListToJsonAsync(),
Max = db.Query<Category>().Max(a => a.CategoryId),//解析成Sql
MaxAsync = db.Query<Category>().MaxAsync(a => a.CategoryId),
Min = db.Query<Category>().Min(a => a.CategoryId),//解析成Sql
MinAsync = db.Query<Category>().MinAsync(a => a.CategoryId),
Count = db.Query<Category>().Count(),//解析成Sql
CountAsync = db.Query<Category>().CountAsync(),
Sum = db.Query<Category>().Sum(s => s.CategoryId),//解析成Sql
SumAsync = db.Query<Category>().SumAsync(s => s.CategoryId),
Avg = db.Query<Category>().Avg(s => s.CategoryId),//解析成Sql
AvgAsync = db.Query<Category>().AvgAsync(s => s.CategoryId)
});
var data2= query.First();
From子查询
var subQuery2 = db.Query<Product>().Select(s=>new
{
s.ProductId,
s.CategoryId,
s.ProductCode,
s.ProductName,
s.DeleteMark
});
var data = db.Query(subQuery2).ToList();
Join子查询
var subQuery1 = db.Query<Product>().Select(s => new
{
s.ProductId,
s.CategoryId,
s.ProductCode,
s.ProductName,
s.DeleteMark
});
var data = db.Query<Category>().InnerJoin(subQuery1, (a, b) => a.CategoryId == b.CategoryId).ToList();
Include查询
// 联表条件 默认优先匹配主键 其次带有ID结尾的名称
var data = db.Query<Category>().Include(i => i.Products).ToList();
Exists查询
var data = db.Query<Product>()
.Where(w => db.Query<Product>().WhereIF(!string.IsNullOrWhiteSpace("测试"), a => a.ProductId == 1).Select(s => 1).Any())
.Select(s => new
{
s.ProductId,
s.ProductCode
}).ToList();
查询绑定字段(注意 字段必须是公开的,否则绑定外部无法访问,没有意义)
//当某些字段需要参与计算并且不返回前端时推荐用字段绑定,无需从A实体转换到B实体,强烈推荐此方式
var data = db.Query<Product>().Select(s => new Product()
{
_xx = s.ProductName
}).First();
八、Lambda表达式
动态表达式 命名空间 Fast.Framework.Utils
var ex = DynamicWhereExp.Create<Product>().AndIF(1 == 1, a => a.DeleteMark == true).Build();
var data = db.Query<Product>().Where(ex).ToList();
Sql函数 自定义函数 需引入命名空间 Fast.Framework.Utils 使用SqlFunc类
SqlServer
类型转换
方法名称 解析示例值 说明 自定义函数 ToString CONVERT( VARCHAR(255),123) 转换 VARCHAR 否 ToDateTime CONVERT( DATETIME,‘2022-09-16’) 转换 DATETIME 否 ToDecimal CONVERT( DECIMAL(10,6),‘123’) 转换 DECIMAL 否 ToDouble CONVERT( NUMERIC(10,6),‘123’) 转换 NUMERIC 否 ToSingle CONVERT( FLOAT,‘123’) 转换 FLOAT 否 ToInt32 CONVERT( INT,‘123’) 转换 INT 否 ToInt64 CONVERT( BIGINT,‘123’) 转换 BIGINT 否 ToBoolean CONVERT( BIT,‘1’) 转换 BIT 否 ToChar CONVERT( CHAR(2),'x') 转换 CHAR 否 聚合函数
方法名称 解析示例值 说明 自定义函数 Max MAX( a.[xx] ) 最大值 是 Min MIN( a.[xx] ) 最小值 是 Count COUNT( a.[xx] ) 计数 是 Sum SUM( a.[xx] ) 合计 是 Avg AVG( a.[xx] ) 平均 是 数学函数
方法名称 解析示例值 说明 自定义函数 Abs ABS( a.[xx] ) 绝对值 是 Round ROUND( a.[xx] ,2 ) 四舍五入 是 字符串函数
方法名称 解析示例值 说明 自定义函数 StartsWith LIKE 'xx'+‘%’ 左模糊 否 EndsWith LIKE ‘%’+'xx' 右模糊 否 Contains LIKE ‘%’+'xx'+‘%’ 全模糊 否 SubString SUBSTRING( 'xxxxxx' ,1,3) 截取 否 Replace REPLACE( 'xxx','x','y') 替换 否 Len LEN( 'xxx' ) 长度 是 TrimStart LTRIM( ' xx ' ) 修剪起始空格 否 TrimEnd RTRIM( ' xx ' ) 修剪末尾空格 否 ToUpper UPPER( 'xx' ) 大写 否 ToLower LOWER( 'xx' ) 小写 否 Concat CONCAT(a.[xx1],a.[xx2]) 字符串拼接 否 Operation [CreateTime] >= @Now_1 日期、数值、字符串范围比较 是 日期函数
方法名称 解析示例值 说明 自定义函数 DateDiff DATEDIFF( DAY ,a.[xx],b.[xx]) 日期相差 是 AddYears DATEADD( YEAR,a.[xx],1 ) 添加年份 否 AddMonths DATEADD( MONTH,a.[xx],1 ) 添加月份 否 AddDays DATEADD( DAY,a.[xx],1 ) 添加天数 否 AddHours DATEADD( HOUR,a.[xx],1 ) 添加时 否 AddMinutes DATEADD( MINUTE,a.[xx],1 ) 添加分 否 AddSeconds DATEADD( SECOND,a.[xx],1 ) 添加秒 否 AddMilliseconds DATEADD( MILLISECOND,a.[xx],1 ) 添加毫秒 否 Year YEAR( a.[xx] ) 获取年份 是 Month MONTH( a.[xx] ) 获取月份 是 Day DAY( a.[xx] ) 获取天数 是 其它函数
方法名称 解析示例值 说明 自定义函数 NewGuid NEWID() 获取GUID 否 Equals p.[ProductCode] = '123' 比较 否 IsNull ISNULL(a.[xx],0) 是否为空 是 Case CASE case 是 When WHEN when 是 Then THEN then 是 Else ELSE else 是 End END end 是
MySql
类型转换
方法名称 解析示例值 说明 自定义函数 ToString CAST( a.`xx` AS CHAR(510) ) 转换 CHAR(510) 否 ToDateTime CAST( a.`xx` AS DATETIME ) 转换 DATETIME 否 ToDecimal CAST( a.`xx` AS DECIMAL(10,6) ) 转换 DECIMAL(10,6) 否 ToDouble CAST( a.`xx` AS DECIMAL(10,6) ) 转换 DECIMAL(10,6) 否 ToInt32 CAST( a.`xx` AS DECIMAL(10) ) 转换 DECIMAL(10) 否 ToInt64 CAST( a.`xx` AS DECIMAL(19) ) 转换 DECIMAL(19) 否 ToBoolean CAST( a.`xx` AS UNSIGNED ) 转换 UNSIGNED 否 ToChar CAST( a.`xx` AS CHAR(2) ) 转换 CHAR(2) 否 聚合函数
方法名称 解析示例值 说明 自定义函数 Max MAX( a.`xx` ) 最大值 是 Min MIN( a.`xx` ) 最小值 是 Count COUNT( a.`xx` ) 计数 是 Sum SUM( a.`xx` ) 合计 是 Avg AVG( a.`xx` ) 平均 是 数学函数
方法名称 解析示例值 说明 自定义函数 Abs ABS( a.`xx` ) 绝对值 是 Round ROUND( a.`xx` ,2 ) 四舍五入 是 字符串函数
方法名称 解析示例值 说明 自定义函数 StartsWith LIKE CONCAT( 'xx','%' ) 左模糊 否 EndsWith LIKE CONCAT( '%','xx' ) 右模糊 否 Contains LIKE CONCAT( '%','xx','%' ) 全模糊 否 SubString SUBSTRING( 'xxxxxx' ,1,3 ) 截取 否 Replace REPLACE( 'xxx','x','y' ) 替换 否 Len LEN( 'xxx' ) 长度 是 Trim TRIM( ' xx ' ) 修剪空格 否 TrimStart LTRIM( ' xx ' ) 修剪起始空格 否 TrimEnd RTRIM( ' xx ' ) 修剪末尾空格 否 ToUpper UPPER( 'xx' ) 大写 否 ToLower LOWER( 'xx' ) 小写 否 Concat CONCAT(a.`xx1`,a.`xx2`) 字符串拼接 否 Operation `CreateTime` >= @Now_1 日期、数值、字符串范围比较 是 日期函数
方法名称 解析示例值 说明 自定义函数 DateDiff DATEDIFF( a.`xx`,b.`xx` ) 日期相差 返回相差天数 是 TimestampDiff TIMESTAMPDIFF( DAY,a.`xx`,b.`xx` ) 日期相差 指定时间单位 是 AddYears DATE_ADD( a.`xx`,INTERVAL 1 YEAR ) 添加年份 否 AddMonths DATE_ADD( a.`xx`,INTERVAL 1 MONTH ) 添加月份 否 AddDays DATE_ADD( a.`xx`,INTERVAL 1 DAY ) 添加天数 否 AddHours DATE_ADD( a.`xx`,INTERVAL 1 HOUR ) 添加时 否 AddMinutes DATE_ADD( a.`xx`,INTERVAL 1 MINUTE ) 添加分 否 AddSeconds DATE_ADD( a.`xx`,INTERVAL 1 SECOND ) 添加秒 否 AddMilliseconds DATE_ADD( a.`xx`,INTERVAL 1 MINUTE_SECOND ) 添加毫秒 否 Year YEAR( a.`xx` ) 获取年份 是 Month MONTH( a.`xx` ) 获取月份 是 Day DAY( a.`xx` ) 获取天数 是 其它函数
方法名称 解析示例值 说明 自定义函数 NewGuid UUID() 获取GUID 否 Equals p.`ProductCode` = '123' 比较 否 IfNull IFNULL( a.`xx`,0 ) 如果为空 是 Case CASE case 是 When WHEN when 是 Then THEN then 是 Else ELSE else 是 End END end 是
Oracle
类型转换
方法名称 解析示例值 说明 自定义函数 ToString CAST( a."xx" AS VARCHAR(255) ) 转换 VARCHAR 否 ToDateTime TO_TIMESTAMP( a."xx" ,'yyyy-MM-dd hh:mi:ss.ff') 转换 DATETIME 否 ToDecimal CAST( a."xx" AS DECIMAL(10,6) ) 转换 DECIMAL 否 ToDouble CAST( a."xx" AS NUMBER ) 转换 NUMBER 否 ToSingle CAST( a."xx" AS FLOAT ) 转换 FLOAT 否 ToInt32 CAST( a."xx" AS INT ) 转换 INT 否 ToInt64 CAST( a."xx" AS NUMBER ) 转换 NUMBER 否 ToBoolean CAST( a."xx" AS CHAR(1) ) 转换 CHAR 否 ToChar CAST( a."xx" AS CHAR(2) ) 转换 CHAR 否 聚合函数
方法名称 解析示例值 说明 自定义函数 Max MAX( a."xx" ) 最大值 是 Min MIN( a."xx" ) 最小值 是 Count COUNT( a."xx" ) 计数 是 Sum SUM( a."xx" ) 合计 是 Avg AVG( a."xx" ) 平均 是 数学函数
方法名称 解析示例值 说明 自定义函数 Abs ABS( a."xx" ) 绝对值 是 Round ROUND( a."xx" ,2 ) 四舍五入 是 字符串函数
方法名称 解析示例值 说明 自定义函数 StartsWith LIKE CONCAT( 'xx','%' ) 左模糊 否 EndsWith LIKE CONCAT( '%','xx' ) 右模糊 否 Contains LIKE CONCAT( '%','xx','%' ) 全模糊 否 SubString SUBSTRING( 'xxxxxx' ,1,3) 截取 否 Replace REPLACE( 'xxx','x','y') 替换 否 Length LENGTH( 'xxx' ) 长度 是 TrimStart LTRIM( ' xx ' ) 修剪起始空格 否 TrimEnd RTRIM( ' xx ' ) 修剪末尾空格 否 ToUpper UPPER( 'xx' ) 大写 否 ToLower LOWER( 'xx' ) 小写 否 Concat CONCAT(a."xx1",a."xx2") 字符串拼接 否 Operation ”CreateTime“ >= @Now_1 日期、数值、字符串范围比较 是 日期函数
方法名称 解析示例值 说明 自定义函数 Year EXTRACT( YEAR FROM a."xx" ) 获取年份 是 Month EXTRACT( MONTH FROM a."xx" ) 获取月份 是 Day EXTRACT( DAY FROM a."xx" ) 获取天数 是 其它函数
方法名称 解析示例值 说明 自定义函数 Equals p."ProductCode" = '123' 比较 否 Nvl NVL( a."xx",0 ) 空,默认 是 Case CASE case 是 When WHEN when 是 Then THEN then 是 Else ELSE else 是 End END end 是
PostgreSql
类型转换
方法名称 解析示例值 说明 自定义函数 ToString a."xx"::VARCHAR(255) 转换 VARCHAR 否 ToDateTime a."xx"::TIMESTAMP 转换 TIMESTAMP 否 ToDecimal a."xx"::DECIMAL(10,6) 转换 DECIMAL 否 ToDouble a."xx"::NUMERIC(10,6) 转换 NUMERIC 否 ToSingle a."xx"::REAL 转换 REAL 否 ToInt32 a."xx"::INTEGER 转换 INT 否 ToInt64 a."xx"::BIGINT 转换 BIGINT 否 ToBoolean a."xx"::BOOLEAN 转换 BOOLEAN 否 ToChar a."xx"::CHAR(2) 转换 CHAR 否 聚合函数
方法名称 解析示例值 说明 自定义函数 Max MAX( a."xx" ) 最大值 是 Min MIN( a."xx" ) 最小值 是 Count COUNT( a."xx" ) 计数 是 Sum SUM( a."xx" ) 合计 是 Avg AVG( a."xx" ) 平均 是 数学函数
方法名称 解析示例值 说明 自定义函数 Abs ABS( a."xx" ) 绝对值 是 Round ROUND( a."xx" ,2 ) 四舍五入 是 字符串函数
方法名称 解析示例值 说明 自定义函数 StartsWith LIKE CONCAT( '%','xx' ) 左模糊 否 EndsWith LIKE CONCAT( 'xx','%' ) 右模糊 否 Contains LIKE CONCAT( '%','xx','%' ) 全模糊 否 SubString SUBSTRING( 'xxxxxx' ,1,3 ) 截取 否 Replace REPLACE( 'xxx','x','y' ) 替换 否 Length LENGTH( 'xxx' ) 长度 是 Trim TRIM( ' xx ' ) 修剪空格 否 TrimStart LTRIM( ' xx ' ) 修剪起始空格 否 TrimEnd RTRIM( ' xx ' ) 修剪末尾空格 否 ToUpper UPPER( 'xx' ) 大写 否 ToLower LOWER( 'xx' ) 小写 否 Concat CONCAT(a."xx1",a."xx2") 字符串拼接 否 Operation ”CreateTime“ >= @Now_1 日期、数值、字符串范围比较 是 日期函数
方法名称 解析示例值 说明 自定义函数 AddYears a."xx" + INTERVAL '1 YEAR' 添加年份 否 AddMonths a."xx" + INTERVAL '1 MONTH' 添加月份 否 AddDays a."xx" + INTERVAL '1 DAY' 添加天数 否 AddHours a."xx" + INTERVAL '1 HOUR' 添加时 否 AddMinutes a."xx" + INTERVAL '1 MINUTE' 添加分 否 AddSeconds a."xx" + INTERVAL '1 SECOND' 添加秒 否 AddMilliseconds a."xx" + INTERVAL '1 MINUTE_SECOND' 添加毫秒 否 Year YEAR( a."xx" ) 获取年份 是 Month MONTH( a."xx" ) 获取月份 是 Day DAY( a."xx" ) 获取天数 是 查询函数
方法名称 解析示例值 说明 自定义函数 In IN ( a."xx" ,'x1','x2','x3' ) In查询 是 NotIn NOT IN ( a."xx" ,'x1','x2','x3' ) Not In查询 是 其它函数
方法名称 解析示例值 说明 自定义函数 Equals p.”ProductCode“ = '123' 比较 否 Case CASE case 是 When WHEN when 是 Then THEN then 是 Else ELSE else 是 End END end 是
Sqlite
类型转换
方法名称 解析示例值 说明 自定义函数 ToString CAST(a.[xx] AS TEXT ) 转换 TEXT 否 ToDateTime DATETIME( a.[xx] ) 转换 DateTime 否 ToDecimal CAST(a.[xx] AS DECIMAL(10,6) ) 转换 DECIMAL 否 ToDouble CAST(a.[xx] AS NUMERIC(10,6) ) 转换 NUMERIC 否 ToSingle CAST(a.[xx] AS FLOAT ) 转换 FLOAT 否 ToInt32 CAST(a.[xx] AS INTEGER ) 转换 INTEGER 否 ToInt64 CAST(a.[xx] AS BIGINT ) 转换 BIGINT 否 ToBoolean CAST(a.[xx] AS CHAR(1) ) 转换 CHAR 否 ToChar CAST(a.[xx] AS CHAR(2) ) 转换 CHAR 否 聚合函数
方法名称 解析示例值 说明 自定义函数 Max MAX( a.[xx] ) 最大值 是 Min MIN( a.[xx] ) 最小值 是 Count COUNT( a.[xx] ) 计数 是 Sum SUM( a.[xx] ) 合计 是 Avg AVG( a.[xx] ) 平均 是 数学函数
方法名称 解析示例值 说明 自定义函数 Abs ABS( a.[xx] ) 绝对值 是 Round ROUND( a.[xx] ,2 ) 四舍五入 是 字符串函数
方法名称 解析示例值 说明 自定义函数 StartsWith LIKE 'xx'||'%' 左模糊 否 EndsWith LIKE '%'||'xx' 否 Contains LIKE '%'||'xx'||'%' 全模糊 否 SubString SUBSTRING( 'xxxxxx' ,1,3 ) 截取 否 Replace REPLACE( 'xxx','x','y' ) 替换 否 Length LENGTH( 'xxx' ) 长度 是 Trim TRIM( ' xx ' ) 修剪空格 否 TrimStart LTRIM( ' xx ' ) 修剪起始空格 否 TrimEnd RTRIM( ' xx ' ) 修剪末尾空格 否 ToUpper UPPER( 'xx' ) 大写 否 ToLower LOWER( 'xx' ) 小写 否 Operation [CreateTime] >= @Now_1 日期、数值、字符串范围比较 是 日期函数
方法名称 解析示例值 说明 自定义函数 AddYears DATETIME( a.[xx],'1 YEAR' ) 添加年份 否 AddMonths DATETIME( a.[xx],'1 MONTH' ) 添加月份 否 AddDays DATETIME( a.[xx],'1 DAY' ) 添加天数 否 AddHours DATETIME( a.[xx],'1 HOUR' ) 添加时 否 AddMinutes DATETIME( a.[xx],'1 MINUTE' ) 添加分 否 AddSeconds DATETIME( a.[xx],'1 SECOND' ) 添加秒 否 AddMilliseconds DATETIME( a.[xx],'1 YEAR' ) 添加毫秒 否 Year STRFTIME( '%Y', a.[xx] ) 获取年份 是 Month STRFTIME( '%m', a.[xx] ) 获取月份 是 Day STRFTIME( '%j', a.[xx] ) 获取天数 是 其它函数
方法名称 解析示例值 说明 自定义函数 Equals p.”ProductCode“ = '123' 比较 否 Case CASE case 是 When WHEN when 是 Then THEN then 是 Else ELSE else 是 End END end 是
添加自定义函数解析
//注意:只能扩展未实现的方法名称 不能覆盖原有的实现
Models.DbType.MySQL.AddSqlFunc("方法名称", (visit, method, sqlStack) =>
{
//解析逻辑
});
九、数据库日志
db.Aop.DbLog = (sql, dp) =>
{
Console.WriteLine($"执行Sql:{sql}");
if (dp != null)
{
foreach (var item in dp)
{
Console.WriteLine($"参数名称:{item.ParameterName} 参数值:{item.ParameterValue}");
}
}
};
十、事务
普通事务
try
{
db.Ado.BeginTran();//开启事务 // 执行 CRUD db.Ado.CommitTran();//提交事务
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
db.Ado.RollbackTran();//回滚事务
}
更大范围的事务
try
{
db.BeginTran();//开启事务 // 执行 CRUD db.CommitTran();//提交事务
}
catch (Exception ex)
{
db.RollbackTran();//回滚事务
Console.WriteLine(ex.Message);
}
十一、多租户
改变数据库
//数据库配置可从Json配置文件加载
IDbContext db = new DbContext(new List<DbOptions>() {
new DbOptions()
{
DbId = "1",
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=0;max pool size=100;connect timeout=120;"
},
new DbOptions()
{
DbId = "2",
DbType = Models.DbType.MySQL,
ProviderName = "MySqlConnector",
FactoryName = "MySqlConnector.MySqlConnectorFactory,MySqlConnector",
ConnectionStrings = "server=localhost;database=Test;user=root;pwd=123456789;port=3306;min pool size=0;max pool size=100;connect timeout=120;"
}});
db.ChangeDb("2");//切换到MySQL
十二、原生特性支持
/// <summary>
/// 产品
/// </summary>
[Table("ProductMain")]
public class Product
{
/// <summary>
/// 产品ID
/// </summary>
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ProductId { get; set; }
/// <summary>
/// 产品编号
/// </summary>
[Column("ProductCode")]//不标记默认取当前属性名称
public string ProductCode { get; set; }
/// <summary>
/// 自定义1
/// </summary>
[NotMapped]
public string Custom1 { get; set; }
/// <summary>
/// 自定义2
/// </summary>
[Column(TypeName="Json")]//类型标记为Json格式对象 匿名对象属性以及字典值类型 IsClass&&!type.Equals(typeof(string)) 将自动序列化成Json格式
public Category Custom2 { get; set; }
}
十三、原生Ado
// 原始起步
// var conn = db.Ado.DbProviderFactory.CreateConnection();
// var cmd = conn.CreateCommand();
// 封装的方法分别以Execute和Create开头以及预处理 PrepareCommand 方法
// 该方法可以自动帮你处理执行的预操作,主要作用是代码复用。
// 当有非常复杂的查询 ORM不能满足需求的时候可以使用原生Ado满足业务需求
// 构建数据集核心扩展方法 分别有 FirstBuild ListBuild DictionaryBuild DictionaryListBuild
var data = db.Ado.ExecuteReader(CommandType.Text, "select * from product", null).ListBuild<Product>();
十四、工作单元
注册数据库上下文和工作单元服务
var builder = WebApplication.CreateBuilder(args); var configuration = builder.Configuration; // 添加数据库上下文服务
builder.Services.AddFastDbContext();
// 添加工作单元服务
builder.Services.AddUnitOfWork(); // 加载数据库配置
builder.Services.Configure<List<DbOptions>>(configuration.GetSection("DbConfig"));
实际应用
/// <summary>
/// 工作单元
/// </summary>
private readonly IUnitOfWork unitOfWork; /// <summary>
/// 构造方法
/// </summary>
/// <param name="unitOfWork">工作单元</param>
public UnitOfWorkTestService(IUnitOfWork unitOfWork)
{
this.unitOfWork = unitOfWork;
} /// <summary>
/// 测试
/// </summary>
/// <returns></returns>
public string Test()
{
//unitOfWork 对象无需显示使用using
var result1 = unitOfWork.Db.Insert(new Category()
{
CategoryName = "类别3"
}).Exceute(); var result2 = unitOfWork.Db.Insert(new Product()
{
ProductCode = "测试工作单元",
}).Exceute(); unitOfWork.Commit(); return "工作单元执行完成...";
}
十五、大数据导入
批复制 仅支持SqlServer Oracle MySql PostgreSql
var list = new List<Product>();
for (int j = 1; j <= 100000; j++)
{
list.Add(new Product()
{
CategoryId = 1,
ProductCode = $"测试编号_{Timestamp.CurrentTimestampSeconds()}_{j}",
ProductName = $"测试名称_{Timestamp.CurrentTimestampSeconds()}_{j}",
CreateTime = DateTime.Now,
Custom1 = $"测试自定义1_{Timestamp.CurrentTimestampSeconds()}_{j}",
Custom2 = $"测试自定义2_{Timestamp.CurrentTimestampSeconds()}_{j}",
Custom3 = $"测试自定义3_{Timestamp.CurrentTimestampSeconds()}_{j}",
Custom4 = $"测试自定义4_{Timestamp.CurrentTimestampSeconds()}_{j}",
Custom5 = $"测试自定义5_{Timestamp.CurrentTimestampSeconds()}_{j}",
Custom6 = $"测试自定义6_{Timestamp.CurrentTimestampSeconds()}_{j}",
Custom7 = $"测试自定义7_{Timestamp.CurrentTimestampSeconds()}_{j}",
Custom8 = $"测试自定义8_{Timestamp.CurrentTimestampSeconds()}_{j}",
Custom9 = $"测试自定义9_{Timestamp.CurrentTimestampSeconds()}_{j}",
Custom10 = $"测试自定义10_{Timestamp.CurrentTimestampSeconds()}_{j}",
Custom11 = $"测试自定义11_{Timestamp.CurrentTimestampSeconds()}_{j}",
Custom12 = $"测试自定义12_{Timestamp.CurrentTimestampSeconds()}_{j}",
});
}
db.Fast<Product>().BulkCopy(list);
自研、好用、够快、稳定、代码可读性强的ORM的更多相关文章
- 够快网盘支持与iOS-ASIHTTPRequest框架学习
够快网盘支持与iOS-ASIHTTPRequest框架学习 前段时间在公司的产品中支持了够快网盘,用于云盘存储. 在这个过程中,学习到了很多新的知识,也遇到了很多问题,在此记录一下. 首先就够快的AP ...
- 云计算:创业的好时机——上海够快网络科技有限公司总经理蒋烁淼专访(评价阿里云的OSS的4个优点)(够快科技正式宣布已成功挂牌新三板)
云存储是云计算目前的热点之一,Dropbox.Box等产品的风靡,公司因此获得极高估值,都印证了这一点.但云存储对技术和资金要求都比较高,竞争也非常激烈,挑战巨大.国外云存储公司有亚马逊的云平台作为支 ...
- 面试官:如何写出让 CPU 跑得更快的代码?
前言 代码都是由 CPU 跑起来的,我们代码写的好与坏就决定了 CPU 的执行效率,特别是在编写计算密集型的程序,更要注重 CPU 的执行效率,否则将会大大影响系统性能. CPU 内部嵌入了 CPU ...
- XLNet预训练模型,看这篇就够了!(代码实现)
1. 什么是XLNet XLNet 是一个类似 BERT 的模型,而不是完全不同的模型.总之,XLNet是一种通用的自回归预训练方法.它是CMU和Google Brain团队在2019年6月份发布的模 ...
- 性能最快的代码分析工具,Ruff 正在席卷 Python 圈!
几天前,Python 开源社区又出了一个不小的新闻:HTTPX 和 Starlette 在同一天将在用的代码分析工具(flake8.autoflake 和 isort)统一替换成了 Ruff. HTT ...
- Go1.7改善了编译速度并且会生成更快的代码
Go1.7的开发周期正在接近它的下一个里程碑,Go的提交者Dave Cheney报告了子即将发布的版本中,团队成员在语言工具链上的努力. Cheney称,基于当前的开发状态,Go1.7将会很容易就成为 ...
- 你的联动够快够小吗——基于Telerik(ASP.NET平台)
一.目的 这篇文章主要是以如何实现联动下拉为核心,其要原理是利用两个下拉控件.默认两个下拉均为未选择状态,并且 父下拉中存在数据,子下来中没有数据.只有当用户选择某一个父下拉中的某条数据后,子下拉中才 ...
- 并发编程-硬件加持的CAS操作够快么?
Talk is cheap CAS(Compare And Swap),即比较并交换.是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数--内存位置(V).预期原值(A)和新 ...
- 快读代码level.2
long long read() { long long ans=0; char last=' ',ch=getchar();//last用来存正负号,并消去那些换行符,空格 ') { last=ch ...
- javascript 代码可读性
可读性的大部分内容都是和代码缩进相关的,必须保证代码有良好的格式.可读性的另一方面就是注释,一般而言,有如下一些地方需要进行注释 1.1.1 函数和方法 每个函数或方法都应该包含一个注释,描述其目的和 ...
随机推荐
- Pycharm里Python运行窗口显示乱码���的解决方法
当你的Python程序运行后,会在运行窗口中显示乱码 ��� 等字样,如下 原因是 Pycharm中默认设置只显示UTF-8编码的格式,需要修改支持显示中文支持. 解决方法: 菜单中选择 File S ...
- docker 安装redis 6.0.8哨兵集群(一主两从三哨兵)
准备三台主机并且安装了docker 192.168.31.132 192.168.31.134 192.168.31.144 linux 版redis6.0.8 下载 下载地址:https://dow ...
- nacos适配达梦、瀚高、人大金仓数据库及部分源码探究
一.插件实现 1.插件目录结构 2.pom依赖 <dependency> <groupId>com.alibaba.nacos</groupId> <arti ...
- KVM VM set net moel virtio and mulltiqueues
check $ qemu-kvm -net nic,model=help qemu: Supported NIC models: ne2k_pci,i82551,i82557b,i82559er,rt ...
- QMainWindow类中比较重要的方法
方法和描述 addToolBar():添加工具栏 centralWidget():返回窗口中心的一个空间,未设置时返回NULL menuBar(): 返回主窗口的菜单栏 setCentralWidge ...
- WEB前端资源网站推荐
https://www.bootcss.com/ https://www.bootcdn.cn/ http://www.jeasyui.net/plugins/756.html
- 记一次线上问题 → Deadlock 的分析与优化
开心一刻 今天女朋友很生气 女朋友:我发现你们男的,都挺单纯的 我:这话怎么说 女朋友:脑袋里就只想三件事,搞钱,跟谁喝点,还有这娘们真好看 我:你错了,其实我们男人吧,每天只合计一件事 女朋友:啥事 ...
- Unity的BuildPlayerProcessor:深入解析与实用案例
Unity BuildPlayerProcessor Unity BuildPlayerProcessor是Unity引擎中的一个非常有用的功能,它可以让开发者在构建项目时自动执行一些操作.这个功能可 ...
- 2023-08-06:小青蛙住在一条河边, 它想到河对岸的学校去学习 小青蛙打算经过河里 的石头跳到对岸 河里的石头排成了一条直线, 小青蛙每次跳跃必须落在一块石头或者岸上 给定一个长度为n的数组ar
2023-08-06:小青蛙住在一条河边, 它想到河对岸的学校去学习 小青蛙打算经过河里 的石头跳到对岸 河里的石头排成了一条直线, 小青蛙每次跳跃必须落在一块石头或者岸上 给定一个长度为n的数组ar ...
- 多重断言插件之pytest-assume的简单使用
背景: pytest-assume是Pytest框架的一个扩展,它允许在单个测试用例中多次断言.通常情况下,当一个断言失败时,测试会立即停止执行,而pytest-assume允许我 们继续执行剩余的断 ...