咱們公司2019年web開發已遷移至.NET core,目前有部分平臺隨着用戶量增長,單一數據庫部署已經沒法知足咱們的業務需求,一直在尋找EF CORE讀寫分離解決方案,目前在各大技術論壇上還沒找到很好的方案,根據以前找到的讀寫分離方案,綜合目前EF core 的能力,本身編寫了一套EF core實現mysql讀寫分離的解決方案,目前以應用到正式生產環境(Linux)中,日活躍用戶20W,木有發現明顯BUG,推薦個你們使用,部分代碼參考文章(https://www.cnblogs.com/qtqq/p/6942312.html),廢話很少說直接上代碼:html
1、讀寫分離,採用的是一主多從,主庫進行數據寫操做,從庫進行數據讀操做;對DbContext基類進行改造,構造函數傳入讀或寫枚舉;新建一個類SyDbContext繼承DbContext基類;構造函數傳入WriteAndRead枚舉,用來區別是讀庫仍是寫庫mysql
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 }
2、編寫DbContextFactory工廠類,用於建立DbContext讀/寫實列(注意:DbContext在一個請求週期必須保證明例是惟一,因此編寫一個CallContext類,先判斷當前http請求線程是否有實例,沒有則new一個,保證DbContext線程安全);masterConnectionString是主庫鏈接實列,用於數據的寫操做,slaveConnectionString是從庫鏈接實列,用於數據的讀操做,從庫能夠有多個,咱們這裏採用一主多從機制,隨機分配從庫策略(參數在配置文件進行設置,放在文章最後貼出代碼)具體實現代碼以下:git
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 }
3、改造RepositoryBase倉儲基類,具體代碼以下:web
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">對象集合</param> 396 397 /// <param name="autoSave">是否當即執行保存</param> 398 399 /// <returns></returns> 400 401 public bool Delete(IEnumerable<T> entities, bool autoSave = true) 402 403 { 404 405 int row = 0; 406 407 DbMaster.Set<T>().RemoveRange(entities); 408 409 if (autoSave) 410 411 row = Save(); 412 413 return (row > 0); 414 415 } 416 417 418 419 /// <summary> 420 421 /// 獲取實體集合 422 423 /// </summary> 424 425 /// <returns></returns> 426 427 public virtual IQueryable<T> GetList() 428 429 { 430 431 return DbSlave.Set<T>().AsQueryable(); 432 433 } 434 435 436 437 /// <summary> 438 439 /// 根據lambda表達式條件獲取單個實體 440 441 /// </summary> 442 443 /// <param name="predicate">lambda表達式條件</param> 444 445 /// <returns></returns> 446 447 public virtual IQueryable<T> GetList(Expression<Func<T, bool>> predicate) 448 449 { 450 451 return DbSlave.Set<T>().Where(predicate); 452 453 } 454 455 456 457 /// <summary> 458 459 /// 根據lambda表達式條件獲取實體集合 460 461 /// </summary> 462 463 /// <param name="top">前幾條</param> 464 465 /// <param name="predicate">查詢條件</param> 466 467 /// <param name="ordering">排序</param> 468 469 /// <param name="args">條件參數</param> 470 471 /// <returns></returns> 472 473 public virtual IQueryable<T> GetList(int top, string predicate, string ordering, params object[] args) 474 475 { 476 477 var result = DbSlave.Set<T>().AsQueryable(); 478 479 480 481 if (!string.IsNullOrWhiteSpace(predicate)) 482 483 result = result.Where(predicate, args); 484 485 486 487 if (!string.IsNullOrWhiteSpace(ordering)) 488 489 result = result.OrderBy(ordering); 490 491 492 493 if (top > 0) 494 495 { 496 497 result = result.Take(top); 498 499 } 500 501 return result; 502 503 } 504 505 506 507 /// <summary> 508 509 /// 分頁查詢,返回實體對象 510 511 /// </summary> 512 513 /// <param name="pageIndex">當前頁</param> 514 515 /// <param name="pageSize">頁大小</param> 516 517 /// <param name="predicate">條件</param> 518 519 /// <param name="ordering">排序</param> 520 521 /// <param name="args">條件參數</param> 522 523 /// <returns></returns> 524 525 public virtual IQueryable<T> GetPagedList(int pageIndex, int pageSize, string predicate, string ordering, params object[] args) 526 527 { 528 529 var result = (from p in DbSlave.Set<T>() 530 531 select p).AsQueryable(); 532 533 534 535 if (!string.IsNullOrWhiteSpace(predicate)) 536 537 result = result.Where(predicate, args); 538 539 540 541 if (!string.IsNullOrWhiteSpace(ordering)) 542 543 result = result.OrderBy(ordering); 544 545 546 547 return result.Skip((pageIndex - 1) * pageSize).Take(pageSize); 548 549 } 550 551 552 553 /// <summary> 554 555 /// 獲取記錄總數 556 557 /// </summary> 558 559 /// <param name="predicate">查詢條件</param> 560 561 /// <param name="args">條件參數</param> 562 563 /// <returns></returns> 564 565 public virtual int GetRecordCount(string predicate, params object[] args) 566 567 { 568 569 if (string.IsNullOrWhiteSpace(predicate)) 570 571 { 572 573 return DbSlave.Set<T>().Count(); 574 575 } 576 577 else 578 579 { 580 581 return DbSlave.Set<T>().Where(predicate, args).Count(); 582 583 } 584 585 } 586 587 588 589 /// <summary> 590 591 /// 事務性保存 讀庫 592 593 /// </summary> 594 595 public int Save() 596 597 { 598 599 int result = DbMaster.SaveChanges(); 600 601 return result; 602 603 } 604 605 606 607 #endregion 608 609 } 610 611 }
4、配置文件參數配置:sql
appsetting.json數據庫
{json
"urls": "http://*:5009",緩存
"ConnectionStrings": [安全
//主庫,用於寫操做服務器
{
"ConnectionString": "Server=.;UserId=xxx;PassWord=xxx;Database=xx;Charset=utf8;"
},
//從庫1,用於讀操做能夠有n個
{
"ConnectionString_1":"Server=.;UserId=xxx;PassWord=xxx;Database=xx;Charset=utf8;"
},
//從庫2,用於讀操做能夠有n個
{
"ConnectionString_2":"Server=.;UserId=xxx;PassWord=xxx;Database=xxx;Charset=utf8;"
}
],
"DbCount": 2,//從庫數量
"RedisConnectionString": "ip:端口,defaultdatabase=1",//Redis緩存服務器
"IsRedis": true,//是否啓用Redis緩存
"Logging": {
"IncludeScopes": false,
"LogLevel": {
"Default": "Warning"
}
}
}
5、以上就是所有內容,若有疑問或發現bug請移步QQ羣:855531299共同討論學習;
源碼地址:https://gitee.com/shangyakejiwenhua/sykj