我们公司2019年web开发已迁移至.NET core,目前有部分平台随着用户量增加,单一数据库部署已经无法满足我们的业务需求,一直在寻找EF CORE读写分离解决方案,目前在各大技术论坛上还没找到很好的方案,根据之前找到的读写分离方案,综合目前EF core 的能力,自己编写了一套EF core实现mysql读写分离的解决方案,目前以应用到正式生产环境(Linux)中,日活跃用户20W,木有发现明显BUG,推荐个大家使用,部分代码参考文章(),废话不多说直接上代码:
一、读写分离,采用的是一主多从,主库进行数据写操作,从库进行数据读操作;对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">对象集合</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 }
四、配置文件参数配置:
appsetting.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"
}
}
}