EF core 實現讀寫分離解決方案

   咱們公司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   

相關文章
相關標籤/搜索