前言
今天还在想今天月更的内容,然后下午的时候 FreeSql 的 QQ 群里,竟然传出来了一个瓜。大概意思呢就是说有位有名博主,测了几个 orm 的性能,结果其中有一款国产框架在各方面性能数据都不好看,结果呢,这个数据不好看的框架的作者不乐意了,私信这位测试的博主,让人家删博,改测试规则。。。反正就是觉得面上挂不住了。
其实说起来也不是什么大事儿,而且搞过开发的都知道,性能测试这种事儿,其实说白了,就是闲着没事儿搞一搞,完全没必要太在意,与其让人家删博,还不如自己真的把框架搞好,或者你干脆也出一个测试。
不说了,我也没用过那个框架,就是觉得开源的国产软件应该相互鼓励,没必要这样互相伤害。
推荐理由
说回正题。FreeSql 是国产框架,文档非常完善,功能也非常强大,具体的还是直接看文档吧~~
在实际的生产项目中,我们最乐于见到的,其实是更清晰的项目框架,更合理的分层,更整齐轻巧的代码,来完成更多的功能。虽然使用 ORM 可能的确会带来一些性能上的影响,但我也相信现在上生产的项目,没几个不用 ORM 的,尤其现在 ORM 在性能上的提升已经非常明显,而且更加的规范,完全没必要在自己去用传统的方式搭数据层。
应用案例
同样,官方文档给出的具体例子也非常完善,但我们的项目应该算是深度整合了 freesql 的能力,即支撑了十万+用户级别的网站,也支撑了业务逻辑非常复杂的 OA 系统,所以我就简单的在这举几个具体的使用案例。
注入服务
以我们的 OA 系统为例,我们是 DotnetCore 5 的项目,通过 startup 的形式配置 orm
public void ConfigureServices(IServiceCollection services)
{
//日志初始化
services.ConfigureLog();
// orm框架
services.ConfigureOrm(Configuration);
// 缓存配置
services.ConfigureRedis(Configuration);
// 登录授权,跨域策略,session配置
services.ConfigurePolicy(Configuration);
//mvc配置
services.ConfigureMvc(Configuration);
//其他插件配置
services.ConfigurePlug(Configuration);
}
复制代码
上面这段里,我把其他的配置也放出来了,配置 orm 的入口在第二行
具体的配置提到了静态类里
static IdleBus<IFreeSql> ib = new IdleBus<IFreeSql>(TimeSpan.FromMinutes(10));
public static void ConfigureOrm(this IServiceCollection services, IConfiguration configuration)
{
#region orm框架
ib.Register("db_system", () => new FreeSqlBuilder().UseConnectionString(DataType.SqlServer, configuration.GetConnectionString("CysccSystemConnString")).Build());
ib.Register("db_castic", () => new FreeSqlBuilder().UseConnectionString(DataType.SqlServer, configuration.GetConnectionString("CysccCasticConnString")).Build());
ib.Register("db_robot", () => new FreeSqlBuilder().UseConnectionString(DataType.SqlServer, configuration.GetConnectionString("CysccRobotConnString")).Build());
ib.Register("db_passport", () => new FreeSqlBuilder().UseConnectionString(DataType.SqlServer, configuration.GetConnectionString("PassportConnString")).Build());
ib.Register("db_matchai", () => new FreeSqlBuilder().UseConnectionString(DataType.SqlServer, configuration.GetConnectionString("MatchAIConnString")).Build());
ib.Register("db_checkin", () => new FreeSqlBuilder().UseConnectionString(DataType.SqlServer, configuration.GetConnectionString("CheckInConnString")).Build());
services.AddSingleton(ib);
#endregion
}
复制代码
这里我们和官方的案例的区别就是通过 IdleBus 来搭配 FreeSql,因为 OA 要管理多个项目的后台数据,所以有多个上下文要切换,用 IdleBus 提供的池化管理功能能更好的管理 FreeSql 的回收和释放。
生成模型对象
在正式项目中,用 freesql 来接管关系数据库之后,需要生成对应的模型,这里我们由于是对已有项目的升级,所以采用的是数据库优先的模式。可以通过 freesql 提供的 CLI 来生成对应模型。
具体方式就是先根据项目情况,在 CLI 工具里输入对应的指令,连接串,命名空间等参数,然后就会在指定的目录中生成所有数据模型。(在实际情况中,我们可能并不需要引用所有模型,就可以在项目中创建指定目录,按需把我们需要的模型引入进去)
编写基本操作方法和接口
这里所谓基本操作方法,就是用于让其他方法继承的方法,一些基本的 CURD 操作,没必要所有的业务都重新编写,有一个基本的泛型操作类,其他业务方法就都可以直接继承了,大量精简代码量。
这个,因为我这里基类里的方法太多了,就不都列出来了,简单列一下,因为基本的方法官方的文档里都有。
public class MatchAiRespository<T>: IMatchAiRespository<T> where T : class
{
public class MatchAiRespository<T>: IMatchAiRespository<T> where T : class
{
IdleBus<IFreeSql> fsql = null;
const string conn_str = "db_matchai";
public MatchAiRespository(IdleBus<IFreeSql> fsql)
{
this.fsql = fsql;
}
/// <summary>
/// 分页检索,并返回总数
/// </summary>
/// <param name="predicate"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="total"></param>
/// <returns></returns>
public List<T> getList(Expression<Func<T, bool>> predicate, int pageIndex, int pageSize, out long total)
{
return fsql.Get(conn_str).Select<T>().Where(predicate).Count(out total).Page(pageIndex, pageSize).ToList();
}
/// <summary>
/// 获取记录数
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
public async Task<long> getCountAsync(Expression<Func<T, bool>> predicate)
{
return await fsql.Get(conn_str).Select<T>().Where(predicate).CountAsync();
}
/// <summary>
/// 新增单条条目,返回影响条数
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
public int addItem(T t)
{
return fsql.Get(conn_str).Insert<T>(t).ExecuteAffrows();
}
...
}
}
复制代码
对应的接口层(我是习惯先写实现,然后再对应去写接口,也可以反过来先写接口,然后再去写实现)
public interface IMatchAiRespository<T> where T:class
{
/// <summary>
/// 分页检索,并返回总数
/// </summary>
/// <param name="predicate"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <param name="total"></param>
/// <returns></returns>
List<T> getList(Expression<Func<T, bool>> predicate, int pageIndex, int pageSize, out long total);
/// <summary>
/// 获取记录数(异步)
/// </summary>
/// <param name="predicate"></param>
/// <returns></returns>
Task<long> getCountAsync(Expression<Func<T, bool>> predicate);
/// <summary>
/// 新增单条条目,返回影响条数
/// </summary>
/// <param name="t"></param>
/// <returns></returns>
int addItem(T t);
...
}
复制代码
好了,其实到此,如果是实现一些基本的操作的话,创建对应模型的方法类和接口后,不需要编写任何方法,继承基类后,再控制器里就可以直接使用了。
这里我再列举一个扩展方法,也就是继承了基类,但对应需要实现一些更复杂的操作,接口就不再贴了。
public class CertRecordRepo : MatchAiRespository<Cert_Template>, Interfaces.ICertTemplateRespo
{
private IdleBus<IFreeSql> fsql;
const string conn_str = "db_matchai";
public CertRecordRepo(IdleBus<IFreeSql> fsql) : base(fsql)
{
this.fsql = fsql;
}
/// <summary>
/// 获取证书发放记录
/// </summary>
/// <param name="whereJsonStr"></param>
/// <param name="total"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <returns></returns>
public dynamic GetCertRecords(string whereJsonStr, out long total, int pageIndex = 1, int pageSize = 10)
{
DynamicFilterInfo dyfilter = JsonConvert.DeserializeObject<DynamicFilterInfo>(whereJsonStr);
return fsql.Get(conn_str).Select<Cert_Record>()
.WhereDynamicFilter(dyfilter)
.Count(out total)
.Page(pageIndex, pageSize)
.ToList();
}
/// <summary>
/// 获取证书导出记录(选拔赛赛)
/// </summary>
/// <param name="whereJsonStr"></param>
/// <returns></returns>
public async Task<List<CostumModels.CertRecordModel>> GetCertRecordsForExcel(string whereJsonStr)
{
DynamicFilterInfo dyfilter = JsonConvert.DeserializeObject<DynamicFilterInfo>(whereJsonStr);
var list = await fsql.Get(conn_str).Select<Cert_Record, Cert_Template, VIW_ScratchProgramApplyMain>()
.LeftJoin((a, b, c) => a.Template_id == b.TemplateID)
.LeftJoin((a, b, c) => a.ProjectNo == c.ProjectNo)
.WhereDynamicFilter(dyfilter)
.ToListAsync((a, b, c) => new CostumModels.CertRecordModel
{
CertContent = a.CertContent,
ProgramTypeCaption = c.ProgramTypeCaption,
ProjectNo = a.ProjectNo,
ProgramTypeCaptionParent = c.ProgramTypeCaptionParent,
ProjectSortCaption = c.ProjectSortCaption,
ProjectWorkCaption = c.ProjectWorkCaption,
TeamCaption = c.TeamCaption,
TemplateTypeCaption = b.TemplateTypeCaption,
ProjectTitle = c.ProjectTitle,
Score = c.Score,
//HasScore = c.HasScored,
AwardName = a.AwardName,
ProvinceShortName = c.ProvinceShortName,
CertType = a.CertType,
CertNo = a.CertNum
});
return list;
}
}
复制代码
控制器
控制器里直接通过构造函数注入,或者通过属性注入的方式,把构造好的接口注入进来
[Area("space")]
[Authorize]
public class CertController : Controller
{
private readonly ICertRecordRepo certRepo;
private readonly INpoiExcelOperationService npoi;
private readonly IWebHostEnvironment en;
private readonly IResponseHelper resp;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="certRepo"></param>
/// <param name="resp"></param>
/// <param name="en"></param>
/// <param name="npoi"></param>
public CertController( ICertRecordRepo certRepo,IResponseHelper resp,IWebHostEnvironment en, INpoiExcelOperationService npoi)
{
this.certRepo = certRepo;
this.en = en;
this.resp = resp;
this.npoi = npoi;
}
/// <summary>
/// 获取颁发记录
/// </summary>
/// <param name="whereJsonstr"></param>
/// <param name="pageIndex"></param>
/// <param name="pageSize"></param>
/// <returns></returns>
[ResponseCache(Duration =100,VaryByQueryKeys = new string[] {"whereJsonstr","rid"})]
public IActionResult GetCertRecords(string whereJsonstr, int pageIndex = 1, int pageSize = 10)
{
long total = 0;
return Json(_resp.success(new { items = _recordRepo.GetCertRecords(whereJsonstr, out total, pageIndex, pageSize), total }));
}
/// <summary>
/// 导出表格
/// </summary>
/// <param name="npoi"></param>
/// <param name="whereJsonstr"></param>
/// <returns></returns>
[HttpPost,ValidateAntiForgeryToken]
public async Task<IActionResult> ExportCertRecords([FromServices] INpoiExcelOperationService npoi, string whereJsonstr)
{
var list = await _recordRepo.GetCertRecordsForExcel(whereJsonstr);
return Json(await npoi.ExcelDataExportTemplate($"赛项【{list.First().ProgramTypeCaption}】证书发放记录", list.First().ProgramTypeCaption, list, _en.WebRootPath));
}
}
复制代码
至此,后端的代码,就基本结束了,可以看的控制器里的代码十分简洁,基本每个 action 都可以控制在 1-3 行以内。
好了,view 的部分我就不列了,最后看一下页面效果。
好了,基本就是这样了。今天写的有点杂,但还是推荐大家试一试FreeSql
评论