我們公司2019年web開發已遷移至.NET core,目前有部分平臺隨著用戶量增加,單一資料庫部署已經無法滿足我們的業務需求,一直在尋找EF CORE讀寫分離解決方案,目前在各大技術論壇上還沒找到很好的方案,根據之前找到的讀寫分離方案,綜合目前EF core 的能力,自己編寫了一套EF core實 ...
我們公司2019年web開發已遷移至.NET core,目前有部分平臺隨著用戶量增加,單一資料庫部署已經無法滿足我們的業務需求,一直在尋找EF CORE讀寫分離解決方案,目前在各大技術論壇上還沒找到很好的方案,根據之前找到的讀寫分離方案,綜合目前EF core 的能力,自己編寫了一套EF core實現mysql讀寫分離的解決方案,目前以應用到正式生產環境(Linux)中,日活躍用戶20W,木有發現明顯BUG,推薦個大家使用,部分代碼參考文章(https://www.cnblogs.com/qtqq/p/6942312.html),廢話不多說直接上代碼:
一、讀寫分離,採用的是一主多從,主庫進行數據寫操作,從庫進行數據讀操作;對DbContext基類進行改造,構造函數傳入讀或寫枚舉;新建一個類SyDbContext繼承DbContext基類;構造函數傳入WriteAndRead枚舉,用來區別是讀庫還是寫庫
1 using Microsoft.EntityFrameworkCore; 2 3 4 5 namespace Sykj.Repository 6 7 { 8 9 /// <summary> 10 11 /// 資料庫上下文類 12 13 /// </summary> 14 15 public partial class SyDbContext : DbContext 16 17 { 18 19 /// <summary> 20 21 /// 構造函數 22 23 /// </summary> 24 25 /// <param name="options"></param> 26 27 public SyDbContext(WriteAndRead writeRead) : base(DbContextFactory.GetOptions(writeRead)) 28 29 { 30 31 32 33 } 34 35 36 37 /// <summary> 38 39 /// 映射配置調用 40 41 /// </summary> 42 43 /// <param name="modelBuilder"></param> 44 45 protected override void OnModelCreating(ModelBuilder modelBuilder) 46 47 { 48 49 //應用映射配置 50 51 52 53 base.OnModelCreating(modelBuilder); 54 55 } 56 57 } 58 59 }
二、編寫DbContextFactory工廠類,用於創建DbContext讀/寫實列(註意:DbContext在一個請求周期必須保證實例是唯一,所以編寫一個CallContext類,先判斷當前http請求線程是否有實例,沒有則new一個,保證DbContext線程安全);masterConnectionString是主庫連接實列,用於數據的寫操作,slaveConnectionString是從庫連接實列,用於數據的讀操作,從庫可以有多個,我們這裡採用一主多從機制,隨機分配從庫策略(參數在配置文件進行設置,放在文章最後貼出代碼)具體實現代碼如下:
1 using Microsoft.EntityFrameworkCore; 2 using System; 3 using System.Collections.Concurrent; 4 using System.Threading; 5 using Sykj.Infrastructure; 6 using Microsoft.Extensions.Logging; 7 using Microsoft.Extensions.Logging.Console; 8 9 namespace Sykj.Repository 10 { 11 /// <summary> 12 /// DbContext工廠 13 /// </summary> 14 public class DbContextFactory 15 { 16 static Random r = new Random(); 17 static int dbcount = ConfigurationManager.Configuration["DbCount"].ToInt(); 18 19 /// <summary> 20 /// EF日誌輸出到Console 21 /// </summary> 22 static readonly LoggerFactory LoggerFactory = new LoggerFactory(new[] { new ConsoleLoggerProvider((_, __) => true, true) }); 23 24 /// <summary> 25 /// 獲取DbContext的Options 26 /// </summary> 27 /// <param name="writeRead"></param> 28 /// <returns></returns> 29 public static DbContextOptions<SyDbContext> GetOptions(WriteAndRead writeRead) 30 { 31 string masterConnectionString = ConfigurationManager.Configuration["ConnectionStrings:0:ConnectionString"]; 32 33 //隨機選擇讀資料庫節點 34 var optionsBuilder = new DbContextOptionsBuilder<SyDbContext>(); 35 if (writeRead == WriteAndRead.Read) 36 { 37 int i = r.Next(1, dbcount); 38 string slaveConnectionString = ConfigurationManager.Configuration[string.Format("ConnectionStrings:{0}:ConnectionString_{0}", i)]; 39 optionsBuilder.UseMySql(slaveConnectionString).UseLoggerFactory(LoggerFactory); 40 } 41 else 42 { 43 optionsBuilder.UseMySql(masterConnectionString).UseLoggerFactory(LoggerFactory); 44 } 45 return optionsBuilder.Options; 46 } 47 48 /// <summary> 49 /// 創建ReadDbContext實例 50 /// </summary> 51 /// <returns></returns> 52 public static SyDbContext CreateReadDbContext() 53 { 54 //先從線程獲取實例,保證線程安全 55 SyDbContext dbContext = (SyDbContext)CallContext.GetData("ReadDbContext"); 56 if (dbContext == null) 57 { 58 if (dbcount==1)//如果資料庫數量為1,則不啟用讀寫分離 59 { 60 dbContext = new SyDbContext(WriteAndRead.Write); 61 } 62 else 63 { 64 dbContext = new SyDbContext(WriteAndRead.Read); 65 } 66 CallContext.SetData("ReadDbContext", dbContext); 67 } 68 return dbContext; 69 } 70 71 /// <summary> 72 /// 創建WriteDbContext實例 73 /// </summary> 74 /// <returns></returns> 75 public static SyDbContext CreateWriteDbContext() 76 { 77 //先從線程獲取實例,保證線程安全 78 SyDbContext dbContext = (SyDbContext)CallContext.GetData("WriteDbContext"); 79 if (dbContext == null) 80 { 81 dbContext = new SyDbContext(WriteAndRead.Write); 82 CallContext.SetData("WriteDbContext", dbContext); 83 } 84 return dbContext; 85 } 86 } 87 88 /// <summary> 89 /// 讀庫/寫庫 90 /// </summary> 91 public enum WriteAndRead 92 { 93 Write, 94 Read 95 } 96 97 /// <summary> 98 /// 從線程獲取實例 99 /// </summary> 100 public class CallContext 101 { 102 static ConcurrentDictionary<string, AsyncLocal<object>> state = new ConcurrentDictionary<string, AsyncLocal<object>>(); 103 104 public static void SetData(string name, object data) => 105 state.GetOrAdd(name, _ => new AsyncLocal<object>()).Value = data; 106 107 public static object GetData(string name) => 108 state.TryGetValue(name, out AsyncLocal<object> data) ? data.Value : null; 109 } 110 }
1 using Microsoft.EntityFrameworkCore; 2 3 4 5 namespace Sykj.Repository 6 7 { 8 9 /// <summary> 10 11 /// 資料庫上下文類 12 13 /// </summary> 14 15 public partial class SyDbContext : DbContext 16 17 { 18 19 /// <summary> 20 21 /// 構造函數 22 23 /// </summary> 24 25 /// <param name="options"></param> 26 27 public SyDbContext(WriteAndRead writeRead) : base(DbContextFactory.GetOptions(writeRead)) 28 29 { 30 31 32 33 } 34 35 36 37 /// <summary> 38 39 /// 映射配置調用 40 41 /// </summary> 42 43 /// <param name="modelBuilder"></param> 44 45 protected override void OnModelCreating(ModelBuilder modelBuilder) 46 47 { 48 49 //應用映射配置 50 51 52 53 base.OnModelCreating(modelBuilder); 54 55 } 56 57 } 58 59 }
三、改造RepositoryBase倉儲基類,具體代碼如下:
1 using System; 2 3 using System.Collections.Generic; 4 5 using System.Linq; 6 7 using System.Linq.Expressions; 8 9 using System.Linq.Dynamic.Core; 10 11 12 13 namespace Sykj.Repository 14 15 { 16 17 /// <summary> 18 19 /// 倉儲基類 20 21 /// </summary> 22 23 /// <typeparam name="T">實體類型</typeparam> 24 25 public abstract class RepositoryBase<T> : IRepository<T> where T : class 26 27 { 28 29 //定義數據訪問上下文對象 30 31 private readonly Lazy<SyDbContext> _dbMaster = new Lazy<SyDbContext>(() => DbContextFactory.CreateWriteDbContext()); 32 33 private readonly Lazy<SyDbContext> _dbSlave = new Lazy<SyDbContext>(() => DbContextFactory.CreateReadDbContext()); 34 35 36 37 /// <summary> 38 39 /// 主庫,寫操作 40 41 /// </summary> 42 43 protected SyDbContext DbMaster => _dbMaster.Value; 44 45 46 47 /// <summary> 48 49 /// 從庫,讀操作 50 51 /// </summary> 52 53 protected SyDbContext DbSlave => _dbSlave.Value; 54 55 56 57 #region 同步 58 59 60 61 /// <summary> 62 63 /// 判斷記錄是否存在 64 65 /// </summary> 66 67 /// <param name="predicate">lambda表達式條件</param> 68 69 /// <returns></returns> 70 71 public bool IsExist(Expression<Func<T, bool>> predicate) 72 73 { 74 75 return DbSlave.Set<T>().Any(predicate); 76 77 } 78 79 80 81 /// <summary> 82 83 /// 新增實體 84 85 /// </summary> 86 87 /// <param name="entity">實體</param> 88 89 /// <param name="autoSave">是否立即執行保存</param> 90 91 /// <returns></returns> 92 93 public bool Add(T entity, bool autoSave = true) 94 95 { 96 97 int row = 0; 98 99 DbMaster.Set<T>().Add(entity); 100 101 if (autoSave) 102 103 row = Save(); 104 105 return (row > 0); 106 107 } 108 109 110 111 /// <summary> 112 113 /// 批量添加 114 115 /// </summary> 116 117 /// <param name="entities">實體列表</param> 118 119 /// <param name="autoSave">是否立即執行保存</param> 120 121 /// <returns></returns> 122 123 public bool AddRange(IEnumerable<T> entities, bool autoSave = true) 124 125 { 126 127 int row = 0; 128 129 DbMaster.Set<T>().AddRange(entities); 130 131 if (autoSave) 132 133 row = Save(); 134 135 return (row > 0); 136 137 } 138 139 140 141 /// <summary> 142 143 /// 更新實體 144 145 /// </summary> 146 147 /// <param name="entity">實體</param> 148 149 /// <param name="autoSave">是否立即執行保存</param> 150 151 public bool Update(T entity, bool autoSave = true) 152 153 { 154 155 int row = 0; 156 157 DbMaster.Update(entity); 158 159 if (autoSave) 160 161 row = Save(); 162 163 return (row > 0); 164 165 } 166 167 168 169 /// <summary> 170 171 /// 更新實體部分屬性 172 173 /// </summary> 174 175 /// <param name="entity">實體</param> 176 177 /// <param name="autoSave">是否立即執行保存</param> 178 179 /// <param name="updatedProperties">要更新的欄位</param> 180 181 /// <returns></returns> 182 183 public bool Update(T entity, bool autoSave = true, params Expression<Func<T, object>>[] updatedProperties) 184 185 { 186 187 int row = 0; 188 189 //告訴EF Core開始跟蹤實體的更改, 190 191 //因為調用DbContext.Attach方法後,EF Core會將實體的State值 192 193 //更改回EntityState.Unchanged, 194 195 DbMaster.Attach(entity); 196 197 if (updatedProperties.Any()) 198 199 { 200 201 foreach (var property in updatedProperties) 202 203 { 204 205 //告訴EF Core實體的屬性已經更改。將屬性的IsModified設置為true後, 206 207 //也會將實體的State值更改為EntityState.Modified, 208 209 //這樣就保證了下麵SaveChanges的時候會將實體的屬性值Update到資料庫中。 210 211 DbMaster.Entry(entity).Property(property).IsModified = true; 212 213 } 214 215 } 216 217 218 219 if (autoSave) 220 221 row = Save(); 222 223 return (row > 0); 224 225 } 226 227 228 229 /// <summary> 230 231 /// 更新實體部分屬性,泛型方法 232 233 /// </summary> 234 235 /// <param name="entity">實體</param> 236 237 /// <param name="autoSave">是否立即執行保存</param> 238 239 /// <param name="updatedProperties">要更新的欄位</param> 240 241 /// <returns></returns> 242 243 public bool Update<Entity>(Entity entity, bool autoSave = true, params Expression<Func<Entity, object>>[] updatedProperties) where Entity : class 244 245 { 246 247 int row = 0; 248 249 //告訴EF Core開始跟蹤實體的更改, 250 251 //因為調用DbContext.Attach方法後,EF Core會將實體的State值 252 253 //更改回EntityState.Unchanged, 254 255 DbMaster.Attach(entity); 256 257 if (updatedProperties.Any()) 258 259 { 260 261 foreach (var property in updatedProperties) 262 263 { 264 265 //告訴EF Core實體的屬性已經更改。將屬性的IsModified設置為true後, 266 267 //也會將實體的State值更改為EntityState.Modified, 268 269 //這樣就保證了下麵SaveChanges的時候會將實體的屬性值Update到資料庫中。 270 271 DbMaster.Entry(entity).Property(property).IsModified = true; 272 273 } 274 275 } 276 277 278 279 if (autoSave) 280 281 row = Save(); 282 283 return (row > 0); 284 285 } 286 287 288 289 /// <summary> 290 291 /// 批量更新實體 292 293 /// </summary> 294 295 /// <param name="entities">實體列表</param> 296 297 /// <param name="autoSave">是否立即執行保存</param> 298 299 public bool UpdateRange(IEnumerable<T> entities, bool autoSave = true) 300 301 { 302 303 int row = 0; 304 305 DbMaster.UpdateRange(entities); 306 307 if (autoSave) 308 309 row = Save(); 310 311 return (row > 0); 312 313 } 314 315 316 317 /// <summary> 318 319 /// 根據lambda表達式條件獲取單個實體 320 321 /// </summary> 322 323 /// <param name="predicate">lambda表達式條件</param> 324 325 /// <returns></returns> 326 327 public T GetModel(Expression<Func<T, bool>> predicate) 328 329 { 330 331 return DbSlave.Set<T>().FirstOrDefault(predicate); 332 333 } 334 335 336 337 /// <summary> 338 339 /// 刪除實體 340 341 /// </summary> 342 343 /// <param name="entity">要刪除的實體</param> 344 345 /// <param name="autoSave">是否立即執行保存</param> 346 347 public bool Delete(T entity, bool autoSave = true) 348 349 { 350 351 int row = 0; 352 353 DbMaster.Set<T>().Remove(entity); 354 355 if (autoSave) 356 357 row = Save(); 358 359 return (row > 0); 360 361 } 362 363 364 365 /// <summary> 366 367 /// 批量刪除 368 369 /// </summary> 370 371 /// <param name="T">對象集合</param> 372 373 /// <returns></returns> 374 375 public bool Delete(IEnumerable<T> entities) 376 377 { 378 379 DbMaster.Set<T>().RemoveRange(entities); 380 381 int row = DbMaster.SaveChanges(); 382 383 return (row > 0); 384 385 } 386 387 388 389 /// <summary> 390 391 /// 批量刪除 392 393 /// </summary> 394 395 /// <param name="T">對象集合</pa