写点什么

DotNet 圈里一个优秀的 ORM——FreeSql

作者:为自己带盐
  • 2022 年 7 月 02 日
  • 本文字数:5559 字

    阅读完需:约 18 分钟

DotNet圈里一个优秀的ORM——FreeSql

前言

今天还在想今天月更的内容,然后下午的时候 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

发布于: 刚刚阅读数: 4
用户头像

学着码代码,学着码人生。 2019.04.11 加入

狂奔的小码农

评论

发布
暂无评论
DotNet圈里一个优秀的ORM——FreeSql_7月月更_为自己带盐_InfoQ写作社区