.Net 經常使用ORM框架對比:EF Core、FreeSql、SqlSuger (上篇)

  前言:
  最近因爲工做須要,須要選用一種ORM框架,也所以對EF Core、FreeSql、SqlSuger做簡單對比。我的認爲各有有優點,存在即合理,否則早就被淘汰了是吧,因此如何選擇因人而議、因項目而議,下面開始正題。
  本篇文章不講解基礎知識,若有須要可移步到相應官網:EF Core官方文檔: https://docs.microsoft.com/zh-cn/ef/,FreeSql官方文檔: http://freesql.net/guide.html,SqlSuger官方文檔: http://www.codeisbug.com/Home/Doc
  環境說明:項目環境ASP .Net Core Web Api,目標框架:.Net 5,依賴包:
 
一:準備數據實體類
 1     /// <summary>
 2     /// 班級
 3     /// </summary>
 4     public class ClassGrade
 5     {
 6         [FreeSql.DataAnnotations.Column(IsIdentity = true, IsPrimary = true)]//FreeSql
 7         [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]//Sugar
 8         [Key,DatabaseGenerated(DatabaseGeneratedOption.Identity)]  //Ef設置自增(int類型默認自增)
 9         public int Id { get; set; }
10         public string Name { get; set; }
11         [SugarColumn(IsIgnore = true)]
12         public virtual ICollection<Student> Students { get; set; }
13         [SugarColumn(IsIgnore = true)]
14         public virtual ICollection<MiddleClassCourse> Classs { get; set; }//
15     }
16     /// <summary>
17     /// 課程
18     /// </summary>
19     public class Course
20     {
21         [FreeSql.DataAnnotations.Column(IsIdentity = true, IsPrimary = true)]//FreeSql
22         [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]//Sugar
23         [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]  //Ef設置自增(int類型默認自增)
24         public int Id { get; set; }
25         public string Name { get; set; }
26         public virtual string Teacher { get; set; }
27         [SugarColumn(IsIgnore = true)]
28         public virtual ICollection<MiddleClassCourse> ClassStudents { get; set; }//班級學生
29         [SugarColumn(IsIgnore = true)]
30         public virtual ICollection<MiddleStudentCourse> Students { get; set; }//選修學生
31     }
32     /// <summary>
33     /// 學生
34     /// </summary>
35     public class Student
36     {
37         [FreeSql.DataAnnotations.Column(IsIdentity = true, IsPrimary = true)]//FreeSql
38         [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]//Sugar
39         [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]  //Ef設置自增(int類型默認自增)
40         public int Id { get; set; }
41         public string Name { get; set; }
42         public int Age { get; set; }
43         public int Sex { get; set; }
44         public int ClassId { get; set; }
45         [SugarColumn(IsIgnore = true)]
46         public virtual ClassGrade Class { get; set; }
47         [SugarColumn(IsIgnore = true)]
48         public virtual ICollection<MiddleStudentCourse> Courses { get; set; }//輔修課、自選課
49     }
50 {
51     /// <summary>
52     /// 中間表(班級-課程)
53     /// </summary>
54     public class MiddleClassCourse
55     {
56         [FreeSql.DataAnnotations.Column(IsIdentity = true, IsPrimary = true)]//FreeSql
57         [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]//Sugar
58         [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]  //Ef設置自增(int類型默認自增)
59         public int Id { get; set; }
60         public int ClassId { get; set; }
61         [SugarColumn(IsIgnore = true)]
62         public virtual ClassGrade Class { get; set; }
63         public int CourseId { get; set; }
64         [SugarColumn(IsIgnore = true)]
65         public virtual Course Course { get; set; }
66     }
67     /// <summary>
68     /// 中間表(學生-課程)
69     /// </summary>
70     public class MiddleStudentCourse
71     {
72         [FreeSql.DataAnnotations.Column(IsIdentity = true, IsPrimary = true)]//FreeSql
73         [SugarColumn(IsPrimaryKey = true, IsIdentity = true)]//Sugar
74         [Key, DatabaseGenerated(DatabaseGeneratedOption.Identity)]  //Ef設置自增(int類型默認自增)
75         public int Id { get; set; }
76         public int CourseId { get; set; }
77         [SugarColumn(IsIgnore = true)]
78         public virtual Course Course { get; set; }
79         public int StudentId { get; set; }
80         [SugarColumn(IsIgnore = true)]
81         public virtual Student Student { get; set; }
82     }
二:Code First
1. EF的流程相對比較複雜,可是功能也更強大,具體流程我在這裏就不仔細敘述了,下面是EF的DbContext類
    public class EfDbContext : DbContext
    {
        /// <summary>
        /// 指定靜態ILoggerFactory
        /// </summary>
        public static readonly ILoggerFactory MyLoggerFactory = LoggerFactory.Create(builder => { builder.AddConsole(); });

        public EfDbContext() { }

        public EfDbContext(DbContextOptions<EfDbContext> options)
            : base(options)
        {
        }

        private string Conn = null;
        public DbContext ToWriteOrRead(string conn)
        {
            Conn = conn;
            return this;
        }

        protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
        {
            if (!optionsBuilder.IsConfigured)
            {
                optionsBuilder.UseLoggerFactory(MyLoggerFactory)
                     //.UseLazyLoadingProxies()
                     .UseSqlServer(Conn);
            }
            optionsBuilder.UseLoggerFactory(MyLoggerFactory);
        }

        protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            #region MyRegion

            {
                //指定主鍵
                //modelBuilder.Entity<ClassGrade>().HasKey(a => a.Id);
                /////設置數據庫架構
                //modelBuilder.HasDefaultSchema("xl");
                /////表名、屬性名映射
                //modelBuilder.Entity<UserInfo>().ToTable("UserInfos", "Zhaoxi").Property(p => p.UserAge).HasColumnName("Age");
                ////設置聯合主鍵
                //modelBuilder.Entity<SysUserRoleMapping>().HasKey(p => new { p.SysUserId, p.SysRoleId }); 
                ////初始化數據
                //modelBuilder.Entity<Company>().HasData(new List<Company>()
                //{
                //});
                ///////表拆分:在數據庫中是一整張表,在代碼層面是多個實體與其對應;
                //modelBuilder.Entity<SysLog>(dob =>
                //{
                //    dob.ToTable("SysLogInfo");
                //    dob.Property(o => o.LogType).HasColumnName("LogType");//配置兩個實體的相同屬性映射到表的同一列
                //    dob.HasOne(o => o.SysLogDetail).WithOne().HasForeignKey<SysLog>(o => o.Id); ; //配置兩個實體的相同屬性映射到表的同一列
                //});
                //modelBuilder.Entity<SysLogDetail>(dob =>
                //{
                //    dob.ToTable("SysLogInfo");
                //    dob.Property(o => o.LogType).HasColumnName("LogType");//配置兩個實體的相同屬性映射到表的同一列 
                //});
            }

            //設置一對多的關係
            modelBuilder.Entity<Student>().HasOne(c => c.Class).WithMany(s => s.Students).HasForeignKey(b => b.ClassId);

            ////多對多關係  
            modelBuilder.Entity<MiddleStudentCourse>(eb =>
            {
                eb.HasOne(p => p.Course).WithMany(u => u.Students).HasForeignKey(u => u.CourseId);
                eb.HasOne(p => p.Student).WithMany(r => r.Courses).HasForeignKey(s => s.StudentId);
            }); 
            modelBuilder.Entity<MiddleClassCourse>(eb => {
                eb.HasOne(p => p.Course).WithMany(u => u.ClassStudents).HasForeignKey(u => u.CourseId);
                eb.HasOne(p => p.Class).WithMany(r => r.Classs).HasForeignKey(s => s.ClassId);
            });
            #endregion
        }


        public DbSet<ClassGrade> Classs { get; set; }
        public DbSet<Student> Students { get; set; }
        public DbSet<Course> Courses { get; set; }
    }
2.FreeSql的流程相對EF就簡單許多了,不須要執行「Add-Migration」、「Update-Database」命令,運行時檢查沒有表自動建立,下面是FreeSql的DbContext類,與EF很類似。
    public class FreeSqlContext: DbContext
    {

        public DbSet<Student> Students { get; set; }
        public DbSet<Course> Courses { get; set; }
        public DbSet<ClassGrade> ClassGrades { get; set; }
        public DbSet<MiddleClassCourse> MiddleClassCourses { get; set; }
        public DbSet<MiddleStudentCourse> MiddleStudentCourses { get; set; }

        //每一個 DbContext 只觸發一次
        protected override void OnModelCreating(ICodeFirst codefirst)
        {
            codefirst.Entity<Student>(eb =>
            {
                eb.HasOne(a => a.Class).HasForeignKey(b => b.ClassId).WithMany(c => c.Students);
            });

            codefirst.Entity<MiddleStudentCourse>(eb =>
            {
                eb.HasOne(a => a.Student).WithMany(t => t.Courses).HasForeignKey(b => b.StudentId);
                eb.HasOne(a => a.Course).WithMany(t => t.Students).HasForeignKey(a => a.CourseId);
            });

            codefirst.Entity<MiddleClassCourse>(eb =>
            {
                eb.HasOne(a => a.Course).WithMany(t => t.ClassStudents).HasForeignKey(a => a.CourseId);
                eb.HasOne(a => a.Class).WithMany(t => t.Students).HasForeignKey(a => a.ClassId);
            });
        }
    }
3.SqlSuger就更簡單了,不須要配置DbContext,配置以下泛型類就能夠了,T爲實體類
    public class SqlSugerContext<T>: SimpleClient<T> where T : class, new()
    {
        public SqlSugerContext(SqlSugarClient context) : base(context)
        {
            context.CodeFirst.SetStringDefaultLength(200).InitTables(typeof(T));//這樣一個表就能成功建立了
        }
    }
    public class ClassGradeService: SqlSugerContext<ClassGrade>
    {
        public ClassGradeService(SqlSugarClient context):base(context)
        {

        }
    }
    public class CourseService: SqlSugerContext<Course>
    {
        public CourseService(SqlSugarClient context) : base(context)
        {
        }
    }
  public class StudentService: SqlSugerContext<Student>
    {
        public StudentService(SqlSugarClient context) : base(context)
        {
        }
    }
  public class MiddleClassCourseCervice : SqlSugerContext<MiddleClassCourse>
    {
        public MiddleClassCourseCervice(SqlSugarClient context) : base(context)
        {

        }
    }
  public class MiddleStudentCourseService : SqlSugerContext<MiddleStudentCourse>
    {
        public MiddleStudentCourseService(SqlSugarClient context) : base(context)
        {

        }
    }
三:配置聲明
1.鏈接字符串(都實現了讀寫分離,因爲只是測試,數據庫主從都是同一個庫,實際上不能這樣寫,否則沒有讀寫分離的意義):
    "EfConnectionStrings": {
        "WriteConnection": "Server=localhost;Database=DbEfCore;Trusted_Connection=True;",
        "ReadConnectionList": [
            "Server=localhost;Database=DbEfCore;Trusted_Connection=True;"
        ]
    },
    "FreeSqlConnectionStrings": "Server=localhost;Database=DbFreeSql;Trusted_Connection=True;",
    "SqlSugerConnectionStrings": "Server=localhost;Database=DbSqlSuger;Trusted_Connection=True;"
2.EF實現讀寫分離須要自行封裝,另外兩個只須要配置好鏈接字符就行了,下面是EF數據庫讀寫分離的實現:
    public enum WriteAndReadEnum
    {
        Write,  //主庫操做
        Read  //從庫操做
    }
  public interface IDbContextFactory { public EfDbContext ConnWriteOrRead(WriteAndReadEnum writeAndRead); }
public class DBConnectionOption { public string WriteConnection { get; set; } public List<string> ReadConnectionList { get; set; } }
public class DbContextFactory : IDbContextFactory { private readonly EfDbContext _Context = new EfDbContext(); private static int _iSeed = 0; private readonly DBConnectionOption _readAndWrite = null; public DbContextFactory(IOptionsMonitor<DBConnectionOption> options) { _readAndWrite = options.CurrentValue; } public EfDbContext ConnWriteOrRead(WriteAndReadEnum writeAndRead) { //判斷枚舉,不一樣的枚舉能夠建立不一樣的Context 或者更換Context連接; switch (writeAndRead) { case WriteAndReadEnum.Write: ToWrite(); break; //選擇連接//更換_Context連接 //選擇連接 case WriteAndReadEnum.Read: ToRead(); break; //選擇連接//更換_Context連接 default: break; } return _Context; } /// <summary> /// 更換成主庫鏈接 /// </summary> /// <returns></returns> private void ToWrite() { string conn = _readAndWrite.WriteConnection; _Context.ToWriteOrRead(conn); } /// <summary> /// 更換成主庫鏈接 /// /// ///策略---數據庫查詢的負載均衡 /// </summary> /// <returns></returns> private void ToRead() { var conn = this._readAndWrite.ReadConnectionList[_iSeed++ % this._readAndWrite.ReadConnectionList.Count];//輪詢; _Context.ToWriteOrRead(conn); } }
3.在Startup.ConfigureServices方法中注入:
            #region FreeSql//DbFreeSql
            var freestr = Configuration.GetSection("FreeSqlConnectionStrings").Value;
            IFreeSql fsql = new FreeSql.FreeSqlBuilder()
                 .UseConnectionString(FreeSql.DataType.SqlServer, freestr)
                 .UseSlave(freestr)//使用從數據庫,支持多個
                 .UseAutoSyncStructure(true) //自動同步實體結構到數據庫
                 .Build(); //請務一定義成 Singleton 單例模式
            services.AddSingleton<IFreeSql>(fsql);
            services.AddFreeDbContext<FreeSqlContext>(options => options.UseFreeSql(fsql));
            #endregion

            #region SqlSuger//DbSqlSuger
            var sugerstr = Configuration.GetSection("SqlSugerConnectionStrings").Value;
            services.AddScoped(options => new SqlSugarClient(new ConnectionConfig()
            {
                ConnectionString = sugerstr,//鏈接符字串
                DbType = DbType.SqlServer,
                IsAutoCloseConnection = true,
                InitKeyType = InitKeyType.Attribute,//從特性讀取主鍵自增信息
                SlaveConnectionConfigs = new List<SlaveConnectionConfig>() {//使用從數據庫,支持多個
                     new SlaveConnectionConfig() { HitRate=10, ConnectionString=sugerstr }
                }
            }));
            services.AddScoped<ClassGradeService>();
            services.AddScoped<CourseService>();
            services.AddScoped<StudentService>();
            services.AddScoped<MiddleStudentCourseService>();
            services.AddScoped<MiddleClassCourseCervice>();
            #endregion

            #region EfCore//DbEfCore
            services.AddDbContext<EfDbContext>(options => options.UseSqlServer("name=EfConnectionStrings:WriteConnection"));
            services.Configure<DBConnectionOption>(Configuration.GetSection("EfConnectionStrings"));//注入多個連接
            services.AddTransient<IDbContextFactory, DbContextFactory>();
            #endregion
四:計時中間件
  1.添加一箇中間件,用於統計請求Api開始進入管道到結束管道全程耗時
 1     public class CalculateExecutionTime
 2     {
 3         private readonly RequestDelegate _next;//下一個中間件
 4         private readonly ILogger _logger;
 5         Stopwatch stopwatch;
 6         public CalculateExecutionTime(RequestDelegate next, ILoggerFactory loggerFactory)
 7         {
 8             if (loggerFactory == null)
 9             {
10                 throw new ArgumentNullException(nameof(loggerFactory));
11             }
12             this._next = next ?? throw new ArgumentNullException(nameof(next));
13             _logger = loggerFactory.CreateLogger<CalculateExecutionTime>();
14         }
15 
16         public async Task Invoke(HttpContext context)
17         {
18             stopwatch = new Stopwatch();
19             _logger.LogInformation($@"====接口{context.Request.Path}開始計時====");
20             stopwatch.Start();//啓動計時器
21             await _next.Invoke(context);
22             stopwatch.Stop();//中止秒錶。
23             _logger.LogInformation($@"====接口{context.Request.Path}耗時:{stopwatch.ElapsedMilliseconds}ms====");
24         }
25     }
26 
27         public static IApplicationBuilder UseCalculateExecutionTime(this IApplicationBuilder app)
28         {
29             if (app == null)
30             {
31                 throw new ArgumentNullException(nameof(app));
32             }
33             return app.UseMiddleware<CalculateExecutionTime>();
34         }

   2.使用中間件,Configure中加入以下代碼javascript

 1 app.UseCalculateExecutionTime();//Api計時 html

五:總結
  到此基本框架就搭建好了,下一篇將分別實現相同功能的三套API進行具體比較。
  就目前來講,EF Core 最複雜學習成本高,同時Code First功能也是最強的,SqlSuger最簡單容易上手,可是沒有嚴格意義上的Code First,只是可以建立表而已。
 
相關文章
相關標籤/搜索