.NET8关于ORM的一次思考

文章目录

前言

琢磨着在.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;
 }
相关推荐
追逐时光者5 小时前
推荐 12 款开源美观、简单易用的 WPF UI 控件库,让 WPF 应用界面焕然一新!
后端·.net
Jagger_5 小时前
敏捷开发流程-精简版
前端·后端
苏打水com6 小时前
数据库进阶实战:从性能优化到分布式架构的核心突破
数据库·后端
间彧7 小时前
Spring Cloud Gateway与Kong或Nginx等API网关相比有哪些优劣势?
后端
间彧7 小时前
如何基于Spring Cloud Gateway实现灰度发布的具体配置示例?
后端
间彧7 小时前
在实际项目中如何设计一个高可用的Spring Cloud Gateway集群?
后端
间彧7 小时前
如何为Spring Cloud Gateway配置具体的负载均衡策略?
后端
间彧7 小时前
Spring Cloud Gateway详解与应用实战
后端
EnCi Zheng9 小时前
SpringBoot 配置文件完全指南-从入门到精通
java·spring boot·后端
烙印6019 小时前
Spring容器的心脏:深度解析refresh()方法(上)
java·后端·spring