当前位置: 首页 > news >正文

.NET8关于ORM的一次思考

文章目录

  • 前言
  • 一、思路
  • 二、实现ODBC=>SqlHelper.cs
  • 三、数据对象实体化
  • 四、SQL生成SqlBuilder.cs
  • 五、参数注入 SqlParameters.cs
  • 六、反射 SqlOrm.cs
  • 七、自定义数据查询
  • 八、总结

前言

琢磨着在.NET8找一个ORM,对比了最新的框架和性能。

框架批量操作性能SQL控制粒度学习成本扩展性
Dapper★★★★☆完全自主依赖扩展库
SqlSugar★★★★☆半自动内置优化
EF Core★★☆☆☆自动生成高度可扩展
ODBC★★☆☆☆完全自主依赖驱动

Dapper在1000条以内和10万以上的数据都是最快的,且粒度小。
毕竟Dapper够轻量:仅26个核心类,无复杂映射配置。

但当我看到它的驱动依旧是System.Data.SqlClient时,我思索,为什么我不自己写一个呢。
毕竟我的逻辑大多在存储过程里。框架也是人家重构出来的嘛,人家的不一定是适合自己的。
尝试着在.net8里重构了一下并记录下来

一、思路

1、泛型实体缩减代码
2、参数化语句,保障性能和安全
3、以dataset为主、model的二次操作为辅,实现数据的快速Josn化,向接口传输
4、事务可选化,因为主要业务的存储过程我可以单独写事务。
5、异步提升效率。
6、使用lamada精简代码

二、实现ODBC=>SqlHelper.cs

Nuget 安装Microsoft.Data.SqlClient包。
主要方法
1、ExecuteNonQuery 返回执行条数,作为编辑用。
2、ExecuteScalar返回执行Object ,作为查询用。
这区分开了就很好实现了,增、改、删我用1、查我用2。思路清晰。

 using (SqlConnection connection = new SqlConnection(connectionString)){await connection.OpenAsync();using (SqlCommand cmd = new SqlCommand(sql, connection)){return await cmd.ExecuteNonQueryAsync();//ExecuteScalar()}
}

主要连接就这个了,使用using 实现资源释放。

加上我们提到的重构一下:

using Microsoft.Data.SqlClient;
using System.Data;
using System.Data.Common;
using System.Diagnostics;
using System.Transactions;namespace DataProcess
{/// <summary>/// ODBC 辅助类/// </summary>public class SqlHelper{public static string ConnectionString = "";private static int timeout = 30;private static string _empty = "null";/// <summary>/// 返回查询结果/// </summary>/// <param name="SqlStr"></param>/// <returns></returns>public static async Task<object> MyTran(List<CmdData> list, string type, bool IsTran){try{using (SqlConnection connection = new SqlConnection(ConnectionString)){await connection.OpenAsync();if (type == "Edit")// 执行SQL 返回影响条数return await Edit(connection, CommandType.Text, list, IsTran);if (type == "QueryDs")// 执行SQL 返回查询结果DataSetreturn await QueryDs(connection, CommandType.Text, list);if (type == "QueryInt")// 执行SQL 返回查询结果 第一行第一列的int return await QueryInt(connection, CommandType.Text, list);elsereturn _empty;}}catch (SqlException ex){Console.WriteLine($"数据库错误:{ex.Message}");return _empty;}}/// <summary>/// 非查询  =>条数  --存储过程 Istran=0/// </summary>/// <returns></returns>public static async Task<object> Edit(SqlConnection connection, CommandType cmdType, List<CmdData> list, bool IsTran){SqlTransaction trans = null;if (IsTran) //是否开启事务trans = connection.BeginTransaction();try{int result = 0;for (int i = 0; i < list.Count; i++)//批量删除{using (SqlCommand cmd = new SqlCommand(list[i].sql, connection)){if (IsTran) cmd.Transaction = trans;cmd.CommandType = cmdType;if (list[i].parameter != null) cmd.Parameters.AddRange(list[i].parameter);cmd.CommandTimeout = timeout; // 超时定义int rst = await cmd.ExecuteNonQueryAsync();if (rst == 0){  //没执行成功if (IsTran) await trans.RollbackAsync();else break;}else result += rst;}if (IsTran) await (trans).CommitAsync();}return result;}catch (Exception e){if (IsTran) await trans.RollbackAsync();return _empty;}}/// <summary>/// 查询  =>数据 Int/// </summary>public static async Task<object> QueryInt(SqlConnection connection, CommandType cmdType, List<CmdData> list){using (SqlCommand cmd = new SqlCommand(list[0].sql, connection)){cmd.CommandType = cmdType;if (list[0].parameter != null) cmd.Parameters.AddRange(list[0].parameter);cmd.CommandTimeout = timeout; // 超时定义try{var rst = await cmd.ExecuteScalarAsync() ;return rst;}catch (Exception e){return _empty;}}}/// <summary>/// 查询  =>数据 DataSet /// </summary>public static async Task<object> QueryDs(SqlConnection connection, CommandType cmdType, List<CmdData> list){using (SqlCommand cmd = new SqlCommand(list[0].sql, connection)){cmd.CommandType = cmdType;if (list[0].parameter != null) cmd.Parameters.AddRange(list[0].parameter);cmd.CommandTimeout = timeout; // 超时定义try{using (DbDataReader reader = await cmd.ExecuteReaderAsync()){DataSet rst = ConvertDataReaderToDataSet(reader);return rst;}}catch (Exception e){return _empty;}}}public static DataSet ConvertDataReaderToDataSet(DbDataReader reader){DataSet dataSet = new DataSet();// 处理第一个结果集DataTable schemaTable = reader.GetSchemaTable();DataTable dataTable = new DataTable();// 自动构建列结构(根据DataReader的元数据)dataTable.Load(reader);  // 此方法自动映射列并填充数据[^2]dataSet.Tables.Add(dataTable);// 处理后续结果集(如果存在)while (!reader.IsClosed && reader.NextResult()){DataTable nextTable = new DataTable();nextTable.Load(reader);dataSet.Tables.Add(nextTable);}return dataSet;}}//存储过程对象public class StoredProc{public string Name { get; set; }public List<ProcParam> param { get; set; }}public class ProcParam {public ProcParam(string Name, object value){this.Name = Name;this.value = value;}public string Name { get; set; }public object value { get; set; }}

webapi 直接给 sqlhelp.connectionString 赋值,就能实现数据连接了。

三、数据对象实体化

要实现泛型,那肯定要先拿到数据实体的model
数据实体手敲那肯定不和谐,现在实现一键转换的工具还是很多的。我使用EF Core Power Tools,

在这里插入图片描述
在这里插入图片描述
连接数据库,并选中所有表
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

这样就能自动生成跟数据库字段对应的表对象实体了。

四、SQL生成SqlBuilder.cs

SQL是生成分两部分,主体、参数。增删查改还是有迹可循的。

using Microsoft.Data.SqlClient;
using Model;
using System;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;namespace DataProcess
{/// <summary>/// T-SQL DIY/// </summary>public class SqlBuilder{#region  编辑Edit/// <summary>/// 实体 泛型 Insert 语句/// </summary>public static string InsertSQL<T>(T entity){Type type = typeof(T);string  columns = "";string values = "";foreach (PropertyInfo prop in type.GetProperties()){if (prop.Name == "Id") continue; // 排除自增主键columns+=$"{prop.Name},";values+=$"@{prop.Name},";}return $"INSERT INTO {type.Name} ({columns.TrimEnd(',')}) VALUES ({values.TrimEnd(',')});";}public static string InsertSQL_id<T>(T entity){return InsertSQL(entity) + "Select @@IDENTITY AS LastID;";}public static string UpdateSQL<T>(T entity){Type type = typeof(T);string temp = "";foreach (PropertyInfo prop in type.GetProperties()){if (prop.Name == "Id") { continue; } // 排除自增主键temp += string.Format("{0}=@{0},", prop.Name);}return $"UPDATE  {type.Name} SET ({temp.TrimEnd(',')} WHERE  @id=id";}public static string DeleteSQL<T>(T entity) {string objectName = entity.GetType().Name;string sql = string.Format("delete from  {0}  where id=@id;", objectName);return sql;}#endregion#region 查询 querypublic static string SelectSQL<T>(string id) where T: new(){string ModelName = getName<T>();string sql = string.Format("Select * from  {0}  where id={1};", ModelName, id);return sql;}public static string  ByPageSQL<T>(string strWhere, string orderby, int startIndex, int endIndex) where T : new(){string ModelName = getName<T>();StringBuilder strSql = new StringBuilder();strSql.Append("Select * FROM ( ");strSql.Append(" Select ROW_NUMBER() OVER (");if (!string.IsNullOrEmpty(orderby.Trim())){strSql.Append("order by T." + orderby);}else{strSql.Append("order by T.id desc");}strSql.Append(")AS Row, T.*  from " + ModelName + " T ");if (!string.IsNullOrEmpty(strWhere.Trim())){strSql.Append(" WHERE " + strWhere);}strSql.Append(" ) TT");strSql.AppendFormat(" WHERE TT.Row between {0} and {1}", startIndex, endIndex);return strSql.ToString();}public static string  ListSQL<T>(string strWhere) where T : new(){string ModelName = getName<T>();if (string.IsNullOrEmpty(strWhere)) strWhere = "1=1";string sql = string.Format("Select * from  {0}  where {1};", ModelName, strWhere);return sql;}public static string CountSQL<T>(string strWhere) where T : new(){string ModelName = getName<T>();if (string.IsNullOrEmpty(strWhere)) strWhere = "1=1";string sql = string.Format("Select count(1) from  {0}  where {1};", ModelName, strWhere);return sql;}public static string ProcSQL(StoredProc T){string columns = "";if(string.IsNullOrEmpty(T.ActionType))columns += $"@ActionType={T.ActionType},";foreach (ProcParam item in T.param){columns += $"@{item.Name}={item.value},";}return $"EXEC {T.Name} {columns.TrimEnd(',')};";}#endregion/// <summary>/// 实例以取表名/// </summary>public static string getName<T>() where T : new(){T t = new T();string name = t.GetType().Name;return name;}}
}

五、参数注入 SqlParameters.cs

using Microsoft.Data.SqlClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.Text;
using System.Threading.Tasks;namespace DataProcess
{/// <summary>/// 实体注入/// </summary>public class SqlParameters{/// <summary>/// for add/// </summary>public static SqlParameter[] NoID(object entity){return entity.GetType().GetProperties().Where(p => p.Name != "Id").Select(p => new SqlParameter($"@{p.Name}", p.GetValue(entity) ?? DBNull.Value)).ToArray();}/// <summary>/// for delete/// </summary>public static SqlParameter[] OnlyID(object entity){return entity.GetType().GetProperties().Where(p => p.Name == "Id").Select(p => new SqlParameter($"@{p.Name}", p.GetValue(entity) ?? DBNull.Value)).ToArray();}/// <summary>/// for update/// </summary>public static SqlParameter[] HaveId(object entity){SqlParameter[] rst = NoID(entity);SqlParameter[] rst2 = OnlyID(entity);rst.Append(rst2[0]);return rst;}}
}

根据加、删、改,分别循环实体的value注入进去。当然我的每个表都是自增id作为主键的。

六、反射 SqlOrm.cs

增、删、改的Edit部分完成了,现在实现 查询部分。
通过循环实体属性,将DataSet转换为model,并完成赋值

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Data;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace DataProcess
{/// <summary>///  DataRow to Model/// </summary>public class SqlOrm{/// <summary>/// 反射/// </summary>public static T ToEntity<T>(DataRow row) where T : new(){//初始化T t = new T();//得到类型Type type = t.GetType();//属性集合PropertyInfo[] ps = type.GetProperties();//赋值、格式转换ps.ToList().ForEach(p =>{var _data = row[p.Name];//值if (_data != null && _data != DBNull.Value)//非空{if (p.PropertyType == typeof(System.Nullable<System.DateTime>))//空时间p.SetValue(t, DateTime.Parse(_data.ToString() ?? "1970-01-01"), null);else if (p.PropertyType == typeof(System.Nullable<System.Decimal>))//空Decimalp.SetValue(t, Decimal.Parse(_data.ToString() ?? "0"), null);else if (p.PropertyType == typeof(System.Nullable<System.Int32>))//Int32p.SetValue(t, Int32.Parse(_data.ToString() ?? "0"), null);elsep.SetValue(t, Convert.ChangeType(_data, p.PropertyType), null);}elsep.SetValue(t, Convert.ChangeType(_data, p.PropertyType), null);});return t;}}
}

七、自定义数据查询

using Microsoft.Data.SqlClient;
using Model;
using System;
using System.Data;
using System.Data.Common;
using System.Text;
using System.Threading.Tasks;namespace DataProcess
{public class Access{public static List<CmdData> list = new List<CmdData>();#region 基础basic/// <summary>/// 单增/// </summary>public static async Task<bool> Add<T>(T entity){string SqlStr = SqlBuilder.InsertSQL<T>(entity);//语句SqlParameter[] parameter = SqlParameters.NoID(entity);//参数list.Add(new CmdData(SqlStr, parameter));int rst = (int)await SqlHelper.MyTran(list, "Edit", true);return rst > 0;}/// <summary>/// 单改/// </summary>public static async Task<bool> Update<T>(T entity){string SqlStr = SqlBuilder.UpdateSQL<T>(entity);SqlParameter[] parameters = SqlParameters.HaveId(entity);list.Add(new CmdData(SqlStr, parameters));int rst = (int)await SqlHelper.MyTran(list, "Edit", true);return rst > 0;}/// <summary>/// 单删/// </summary>public static async Task<bool> Delete<T>(T entity){string SqlStr = SqlBuilder.DeleteSQL<T>(entity);SqlParameter[] parameters = SqlParameters.OnlyID(entity);list.Add(new CmdData(SqlStr, parameters));int rst = (int)await SqlHelper.MyTran(list, "Edit", true);return rst > 0;}/// <summary>/// 单查=>Entity/// </summary>public static async Task<T> GetModel<T>(int id) where T : new(){string SqlStr = SqlBuilder.SelectSQL<T>(id.ToString());list.Add(new CmdData(SqlStr, null));DataSet ds = (DataSet)await SqlHelper.MyTran(list, "QueryDs", true);if (ds.Tables[0].Rows.Count > 0)return SqlOrm.ToEntity<T>(ds.Tables[0].Rows[0]);else return new T();}#endregion#region  扩展 Extend/// <summary>/// 批量增/// </summary>public static async Task<bool> Adds<T>(List<T> listT){foreach (var entity in listT){string SqlStr = SqlBuilder.InsertSQL<T>(entity);//语句SqlParameter[] parameter = SqlParameters.NoID(entity);//参数list.Add(new CmdData(SqlStr, parameter));}int rst = (int)await SqlHelper.MyTran(list, "Edit", true);return rst > 0;}/// <summary>/// 批改/// </summary>public static async Task<bool> Updates<T>(List<T> listT){foreach (var entity in listT){string SqlStr = SqlBuilder.UpdateSQL<T>(entity);SqlParameter[] parameter = SqlParameters.HaveId(entity);list.Add(new CmdData(SqlStr, parameter));}int rst = (int)await SqlHelper.MyTran(list, "Edit", true);return rst > 0;}/// <summary>/// 批删/// </summary>public static async Task<bool> Deletes<T>(List<T> listT){foreach (var entity in listT){string SqlStr = SqlBuilder.DeleteSQL<T>(entity);SqlParameter[] parameters = SqlParameters.OnlyID(entity);list.Add(new CmdData(SqlStr, parameters));}int rst = (int)await SqlHelper.MyTran(list, "Edit", true);return rst > 0;}/// <summary>/// 单增 返回ID/// </summary>public static async Task<int> Add_ID<T>(T entity){string SqlStr = SqlBuilder.InsertSQL_id<T>(entity);//语句SqlParameter[] parameters = SqlParameters.NoID(entity);//参数list.Add(new CmdData(SqlStr, parameters));return (int)await SqlHelper.MyTran(list, "QueryInt", true);}/// <summary>/// 条件查询/// </summary>/// <returns></returns>public static async Task<DataSet> GetList<T>(string strWhere) where T : new(){string SqlStr = SqlBuilder.ListSQL<T>(strWhere);list.Add(new CmdData(SqlStr, null));return (DataSet)await SqlHelper.MyTran(list, "QueryDs", true);}/// <summary>/// 条件查询=》多Entity/// </summary>public static async Task<List<T>> GetModelList<T>(string strWhere) where T : new(){DataSet ds = await GetList<T>(strWhere);List<T> rst = new List<T>();if (ds.Tables[0].Rows.Count > 0){ds.Tables[0].Rows.Cast<DataRow>().ToList().ForEach(p =>{rst.Add(SqlOrm.ToEntity<T>(p));});}return rst;}/// <summary>/// 条件查询=》条数/// </summary>public static async Task<int> RecordCount<T>(string strWhere) where T : new(){string SqlStr = SqlBuilder.ListSQL<T>(strWhere);list.Add(new CmdData(SqlStr, null));return (int)await SqlHelper.MyTran(list, "QueryInt", true);}/// <summary>/// 分页/// </summary>public static async Task<DataSet> GetListByPage<T>(string strWhere, string orderby, int startIndex, int endIndex) where T : new(){string SqlStr = SqlBuilder.ByPageSQL<T>(strWhere, orderby, startIndex, endIndex);list.Add(new CmdData(SqlStr, null));return (DataSet)await SqlHelper.MyTran(list, "QueryDs", true);}/// <summary>/// 自定义查询/// </summary>public static async Task<DataSet> CustomQuery<T>(string [] param, string strWhere) where T : new(){string SqlStr = SqlBuilder.CustomQuerySQL<T>(param,strWhere);list.Add(new CmdData(SqlStr, null));return (DataSet)await SqlHelper.MyTran(list, "QueryDs", true);}/// <summary>/// 存储过程/// </summary>public static async Task<DataSet> ExecProc(StoredProcs proc){// return (DataSet)await SqlHelper.MyTran(SqlStr, null, "Query", 1);string SqlStr = SqlBuilder.ProcSQL(proc);//语句list.Add(new CmdData(SqlStr, null));return (DataSet)await SqlHelper.MyTran(list, "Edit", false);//不启动事务}#endregion}
}

八、总结

5个类完成了自己在 .net8 中的ORM. 不是太复杂。
我的想法是前端传回json到API ,解析成modelList 完成批量的增删改,联表这种数据量大的就丢到存储过程里去把。

//调用存储过程示例
public static async Task<DataSet> MyProc()
{StoredProc proc = new StoredProc();proc.Name = "SP_Test";proc.param.Add(new ProcParam("createtime", "2025-0-13"));proc.param.Add(new ProcParam("name", "张三"));proc.param.Add(new ProcParam("class", "2年纪"));return await Access.ExecProc(proc);
}
//批量修改示例public static async Task<bool> Updates<Users>(string JsonStr){List<Users> List = JsonConvert.DeserializeObject<List<Users>>(JsonStr);return await Access.Updates<Users>(List);}
//分页查询示例public static async Task<string> GetListByPage<User>(){DataSet ds= await Access.GetListByPage<User> ("name like '%张%'"," createtme desc",  101,  200);string JsonStr=JsonConvert.SerializeObject(ds.tables[0]);return JsonStr;}

相关文章:

  • MapReduce 入门实战:WordCount 程序
  • 2025.05.11阿里云机考真题算法岗-第三题
  • MapReduce打包运行
  • JavaEE--初识网络
  • OCR:开启财务数字化变革的魔法钥匙
  • 提示词设计模板(基于最佳实践)
  • springboot3+vue3融合项目实战-大事件文章管理系统-获取文章分类详情
  • BFS算法篇——从晨曦到星辰,BFS算法在多源最短路径问题中的诗意航行(上)
  • 【Android】下拉刷新组件Swiperefreshlayout
  • 力扣算法---总结篇
  • 分式注记种表达方式arcgis
  • Qubes os系统详解
  • Leetcode 3542. Minimum Operations to Convert All Elements to Zero
  • Android之横向滑动列表
  • 每日算法刷题计划Day5 5.13:leetcode数组3道题,用时1h
  • mac 10.15.7 svn安装
  • 《内网渗透测试:绕过最新防火墙策略》
  • C#高级编程:设计模式原则
  • 数据分析预备篇---Pandas的Series
  • langChain存储文档片段,并进行相似性检索
  • 广东早熟荔枝“抢滩”上海,向长三角消费者喊话:包甜,管够
  • 视频丨美国两名男童持枪与警察对峙,一人还试图扣动扳机
  • 马上评丨火车穿村而过多人被撞身亡,亡羊补牢慢不得
  • 吉林:消纳绿电,“氢”装上阵
  • 从采购到销售!市场监管总局指导行业协会防控肉品风险
  • 湛江霞山通报渔船火灾:起火船舶共8艘,无人员伤亡或被困