前言:
最近由於工作需要,需要選用一種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)//注意這裡要有預設值等於null { 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.在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
四:總結
到此基本框架就搭建好了,下一篇將分別實現相同功能的三套API進行具體比較。
就目前來說,EF Core 最複雜學習成本高,同時Code First功能也是最強的,SqlSuger最簡單容易上手,但是沒有嚴格意義上的Code First,只是能夠建立表而已。