文章目录
- 前言
- 一、思路
- 二、实现ODBC=>SqlHelper.cs
- 三、数据对象实体化
- 四、SQL生成SqlBuilder.cs
- [五、参数注入 SqlParameters.cs](#五、参数注入 SqlParameters.cs)
- [六、反射 SqlOrm.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化,方便接口调用、数据IO
4、事务可选化设置,因为主业务在存储过程,我可以另外单独写事务。
5、异步提升效率。
6、使用Lamada精简代码
7、复杂语句在存储过程分离,降低ODBC的维护成本。
8、using实现数据的快速回收。
二、实现ODBC=>SqlHelper.cs
Nuget 安装Microsoft.Data.SqlClient包。
主要方法
1、ExecuteNonQuery 返回执行条数,作为编辑用。
2、ExecuteScalar返回执行Object ,作为查询用。
这区分开了就很好实现了,增、改、删我用1、查我用2。思路清晰。
csharp
using (SqlConnection connection = new SqlConnection(connectionString))
{
await connection.OpenAsync();
using (SqlCommand cmd = new SqlCommand(sql, connection))
{
return await cmd.ExecuteNonQueryAsync();//ExecuteScalar()
}
}
主要连接就这个了,使用using 实现资源释放。
加上我们提到的重构一下:
csharp
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 返回查询结果DataSet
return await QueryDs(connection, CommandType.Text, list);
if (type == "QueryInt")
// 执行SQL 返回查询结果 第一行第一列的int
return await QueryInt(connection, CommandType.Text, list);
else
return _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是生成分两部分,主体、参数。增删查改还是有迹可循的。
csharp
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 查询 query
public 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(StoredProcs T)
{
string columns = "";
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
csharp
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,并完成赋值
csharp
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>))//空Decimal
p.SetValue(t, Decimal.Parse(_data.ToString() ?? "0"), null);
else if (p.PropertyType == typeof(System.Nullable<System.Int32>))//Int32
p.SetValue(t, Int32.Parse(_data.ToString() ?? "0"), null);
else
p.SetValue(t, Convert.ChangeType(_data, p.PropertyType), null);
}
else
p.SetValue(t, Convert.ChangeType(_data, p.PropertyType), null);
});
return t;
}
}
}
七、自定义数据查询
csharp
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
{
#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<CmdData> list = new List<CmdData>();
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<CmdData> list = new List<CmdData>();
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<CmdData> list = new List<CmdData>();
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<CmdData> list = new List<CmdData>();
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)
{
List<CmdData> list = new List<CmdData>();
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)
{
List<CmdData> list = new List<CmdData>();
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)
{
List<CmdData> list = new List<CmdData>();
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<CmdData> list = new List<CmdData>();
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<CmdData> list = new List<CmdData>();
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<CmdData> list = new List<CmdData>();
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<CmdData> list = new List<CmdData>();
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<CmdData> list = new List<CmdData>();
list.Add(new CmdData(SqlStr, null));
return (DataSet)await SqlHelper.MyTran(list, "QueryDs", false);//不启动事务
}
#endregion
}
}
八、总结
5个类完成了自己在 .net8 中的ORM. 不是太复杂。
我的想法是前端传回json到API ,解析成modelList 完成批量的增删改,联表这种数据量大的就丢到存储过程里去把。
csharp
//调用存储过程示例
public static async Task<DataSet> MyProc()
{
StoredProc proc = new StoredProc();
proc.Name = "SP_Test";
proc.param = new List<ProcParam>();
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;
}