.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;
 }
相关推荐
蜗牛沐雨21 分钟前
Rust 中的 `PartialEq` 和 `Eq`:深入解析与应用
开发语言·后端·rust
Python私教23 分钟前
Rust快速入门:从零到实战指南
开发语言·后端·rust
秋野酱1 小时前
基于javaweb的SpringBoot爱游旅行平台设计和实现(源码+文档+部署讲解)
java·spring boot·后端
小明.杨2 小时前
Django 中时区的理解
后端·python·django
有梦想的攻城狮2 小时前
spring中的@Async注解详解
java·后端·spring·异步·async注解
qq_12498707532 小时前
原生小程序+springboot+vue医院医患纠纷管理系统的设计与开发(程序+论文+讲解+安装+售后)
java·数据库·spring boot·后端·小程序·毕业设计
lybugproducer2 小时前
浅谈 Redis 数据类型
java·数据库·redis·后端·链表·缓存
bicijinlian3 小时前
.Net HttpClient 管理客户端(初始化与生命周期管理)
c#·.net·httpclient·.net httpclient·c# http
撸猫7915 小时前
HttpSession 的运行原理
前端·后端·cookie·httpsession