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

时间:2022-09-26 15:29:45

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}");
}
}
};
十、事务
  • 普通事务
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 DictionaryListBuildAsync
var data = await db.Ado.ExecuteReaderAsync(CommandType.Text, "select * from product", null).ListBuildAsync<Product>();