關於orm的個人測試——SqlSugar與FreeSql

前言

轉眼已經過了金九,光陰真的是似箭啊,周六日常加班,忙裡抽閑就想鼓搗個啥看看,剛好最近想著有沒有必要換個orm,從當時原生到Dapper,又到現在的Sqlsugar,因為經常聽到幾個不錯的orm,就是今天想測試的Freesql,其實對於造輪子這種事,個人覺得其實是件好事,只有輪子多了,才會有車,雖然參差不齊,但開車的心情還是挺嗨皮的,就算磕磕絆絆,那也是體驗過才知道,當然畢竟是開源的自己也可以擴展改造嘛。

開始

因為電腦上只有mysql,這裡就單對mysql做下對比測試了,針對增刪改查這些常規操作看下性能對比(這裡暫時只對sqlsugar與freesql對比,至於其他的,可以加可以加)。

測試環境

  • net core 3.1
  • FreeSql 1.8.1
  • sqlSugarCore 5.0.0.15
  • mysql 5.5.53(wc,這麼低)

準備工作

新建個控制台工程,引入兩個nuget包FreeSqlsqlSugarCore,先來個實體吧。

這裡注意下,freesql這個庫沒有對應的擴展包,例如mysql還需要引入FreeSql.Provider.MySql,具體可看對應文檔

    [Table(Name = "student_free")]
    [SugarTable("student_sugar")]
    public class StudentEntity
    {
        [Column(IsIdentity = true, IsPrimary = true)]
        [SugarColumn(IsIdentity = true,IsPrimaryKey = true)]
        public int ID { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public string Number { get; set; }
        public int Sex { get; set; }
        public string Location { get; set; }
        [Column(Name = "class_id")]
        [SugarColumn(ColumnName = "class_id")]
        public int ClassID { get; set; }
    }

接下來對應實現兩個類庫的方法類。

freesql
    public class FreeSqlUtil
    {
        private static readonly string CONNECTION_STRING = "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root; Initial Catalog=test;Charset=utf8;SslMode=none;Min Pool Size=20;Max Pool Size=20";

        private static IFreeSql _fsql;

        private static readonly object locker = new object();

        public static IFreeSql GetInstance()
        {
            if (_fsql == null)
            {
                lock (locker)
                {
                    if (_fsql == null)
                    {
                        _fsql = new FreeSqlBuilder()
                                .UseConnectionString(DataType.MySql, CONNECTION_STRING)
                                // .UseAutoSyncStructure(true)
                                .UseLazyLoading(true)
                                .Build();
                    }
                }
            }
            return _fsql;
        }
    }

當然這裡的sql連接串記得替換成自己的。

    public class FreeSqlTest
    {

        public static List<StudentEntity> GetList(int index, int limit)
        {
            return FreeSqlUtil.GetInstance()
                .Select<StudentEntity>()
                .Page(index, limit)
                .ToList();
        }

        public static List<StudentEntity> GetList(int limit)
        {
            return FreeSqlUtil.GetInstance()
                .Select<StudentEntity>()
                .Limit(limit)
                .ToList();
        }

        public static long Insert(StudentEntity entity)
        {
            long result = FreeSqlUtil.GetInstance()
                                         .Insert(entity)
                                         .ExecuteIdentity();
            return result;
        }

        public static void InsertList(IEnumerable<StudentEntity> entities)
        {
            FreeSqlUtil.GetInstance()
                        .Insert(entities)
                        .ExecuteAffrows();
        }

        public static bool Update(StudentEntity entity)
        {
            int result = FreeSqlUtil.GetInstance()
                .Update<StudentEntity>()
                .SetSource(entity)
                .ExecuteAffrows();
            return result > 0;
        }

        public static void UpdateList(IEnumerable<StudentEntity> entities)
        {
            FreeSqlUtil.GetInstance()
                .Update<StudentEntity>()
                .SetSource(entities)
                .ExecuteAffrows();
        }
    }
sqlsugar

這裡可以參考我之前文章中專門介紹sqlsugar時整理的util,當然也可以參考我的demo工程April.WebApi

    public class BaseDbContext
    {
        public SqlSugarClient Db;

        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="connStr">資料庫連接串</param>
        /// <param name="sqlType">資料庫類型</param>
        public BaseDbContext(string connStr, int sqlType = 1)
        {
            InitDataBase(connStr, sqlType);
        }

        /// <summary>
        /// 構造函數
        /// </summary>
        /// <param name="serverIp">伺服器IP</param>
        /// <param name="user">用戶名</param>
        /// <param name="pass">密碼</param>
        /// <param name="dataBase">資料庫</param>
        public BaseDbContext(string serverIp, string user, string pass, string dataBase)
        {
            string connStr = $"server={serverIp};user id={user};password={pass};persistsecurityinfo=True;database={dataBase}";
            InitDataBase(connStr);
        }

        /// <summary>
        /// 初始化資料庫連接
        /// </summary>
        /// <param name="listConn">連接字元串</param>
        private void InitDataBase(string connStr, int sqlType = 1)
        {
            Db = new SqlSugarClient(new ConnectionConfig()
            {
                ConnectionString = connStr,
                DbType = (DbType)sqlType,
                IsAutoCloseConnection = true,
                //SlaveConnectionConfigs = slaveConnectionConfigs
            });
            Db.Ado.CommandTimeOut = 30000;//設置超時時間
            Db.Aop.OnLogExecuted = (sql, pars) => //SQL執行完事件
            {
                //這裡可以查看執行的sql語句跟參數
            };
            Db.Aop.OnLogExecuting = (sql, pars) => //SQL執行前事件
            {
                //這裡可以查看執行的sql語句跟參數
            };
            Db.Aop.OnError = (exp) =>//執行SQL 錯誤事件
            {
                //這裡可以查看執行的sql語句跟參數
            };
            Db.Aop.OnExecutingChangeSql = (sql, pars) => //SQL執行前 可以修改SQL
            {
                return new KeyValuePair<string, SugarParameter[]>(sql, pars);
            };
        }
        /// <summary>
        /// 開啟事務
        /// </summary>
        public void BeginTran()
        {
            Db.Ado.BeginTran();
        }
        /// <summary>
        /// 提交事務
        /// </summary>
        public void CommitTran()
        {
            Db.Ado.CommitTran();
        }
        /// <summary>
        /// 回滾事務
        /// </summary>
        public void RollbackTran()
        {
            Db.Ado.RollbackTran();
        }
    }

下面是對應的測試方法實現,其中強調一點,對於mysql中有個max_allow_packet這個配置,批量操作的時候會拋出這個異常,當然這裡可以通過修改mysql的配置,不過個人建議還是儘可能通過拆解分次,應該也沒有動不動甩個萬八數據吧(有了輕噴),所以這裡調整了插入方法的實現。

    public class SqlSugarTest
    {
        private static readonly string CONNECTION_STRING = "Data Source=127.0.0.1;Port=3306;User ID=root;Password=root; Initial Catalog=test;Charset=utf8;SslMode=none;Min Pool Size=20;Max Pool Size=20";
        private static BaseDbContext baseDb;
        private static SqlSugarClient db;

        static SqlSugarTest()
        {
            baseDb = new BaseDbContext(CONNECTION_STRING, 0);
            db = baseDb.Db;
        }

        public static List<StudentEntity> GetList(int index,int limit)
        {
            return db.Queryable<StudentEntity>().ToPageList(index, limit);
        }
        public static List<StudentEntity> GetList(int limit)
        {
            return db.Queryable<StudentEntity>().Take(limit).ToList();
        }

        public static int Insert(StudentEntity entity)
        {
            int result = db.Insertable(entity).ExecuteReturnIdentity();
            return result;
        }

        public static void InsertList(IEnumerable<StudentEntity> entities)
        {
            if (entities.Count() >= 10000)
            {
                int count = entities.Count();
                int index = 0;
                Console.WriteLine($"批量插入{count}數據");
                while (count / 5000 > 0)
                {
                    var data = entities.Skip(5000 * index).Take(5000);
                    db.Insertable(data.ToArray())
                .ExecuteCommand();
                    count -= 5000;
                    index++;
                }
                if (count % 5000 > 0)
                {
                    var data = entities.Skip(5000 * index).Take(5000);
                    db.Insertable(data.ToArray())
                .ExecuteCommand();
                    index++;
                }
                Console.WriteLine($"拆解執行{index}次");
            }
            else
            {
                db.Insertable(entities.ToArray())
                .ExecuteCommand();
            }
        }

        public static bool Update(StudentEntity entity)
        {
            int result = db.Updateable(entity).ExecuteCommand();
            return result > 0;
        }

        public static void UpdateList(IEnumerable<StudentEntity> entities)
        {
            if (entities.Count() >= 10000)
            {
                int count = entities.Count();
                int index = 0;
                Console.WriteLine($"批量修改{count}數據");
                while (count / 5000 > 0)
                {
                    var data = entities.Skip(5000 * index).Take(5000);
                    db.Updateable(data.ToArray())
                .ExecuteCommand();
                    count -= 5000;
                    index++;
                }
                if (count % 5000 > 0)
                {
                    var data = entities.Skip(5000 * index).Take(5000);
                    db.Updateable(data.ToArray())
                .ExecuteCommand();
                    index++;
                }
                Console.WriteLine($"拆解執行{index}次");
            }
            else
            {
                db.Updateable(entities.ToArray())
                .ExecuteCommand();
            }
        }
    }

開始測試

測試部分的程式碼就沒啥發的,一個圖。
測試

新增(下面有補充)

新增總共測試3次吧,基本時間都是保持一個穩定的。
新增

新增

新增

查詢(下面有補充)

查詢這裡用的是分頁方法,庫里數據就是單次執行插入測試的數據,總共163006條。
查詢
查詢

修改(下面有補充)

修改
修改
修改

補充測試

看了下freesql入門文章,發現配置這塊兒這樣寫,不使用命令參數化,在新增和修改的速度就很明顯了,當然實際使用的時候還是要注意,畢竟注入還是可怕的。

補充

新增

新增

查詢

查詢

修改

修改

小結

雖然不知道這個測試結果有沒有什麼問題(畢竟沒咋用過),不過通過時間上來看 ,FreeSql在速度上確實是有優勢,當然這裡只是基礎的方法測試,沒有什麼各種功能的嘗試,不過畢竟常用的還是增刪改查嘛,再鼓搗什麼demo的時候可以考慮下嘗試freesql了,雖然目前看來對於現工程的改動還不是太迫切,也希望輪子越來越多,越來越好,當然自己沒事也會繼續鼓搗,學無止境,路漫漫

Tags: