EF6的多线程与分库架构设计实现
1.项目背景
这里简单介绍一下项目需求背景,之前公司的项目基于EF++Repository+UnitOfWork的框架设计的,其中涉及到的技术有RabbitMq消息队列,Autofac依赖注入等常用的.net插件。由于公司的发展,业务不断更新,变得复杂起来,对于数据的实时性、存储容量要求也提高了一个新的高度。数据库上下文DbContext设计的是单例模式,基本上告别了多线程高并发的数据读写能力,看了园子里很多大神的博客,均为找到适合自己当前需求的DbContext的管理方式。总结目前主要的管理方式:
1)DbContext单例模式(长连接)。即公司之前的设计。很明显,这种设计方式无法支持多线程同步数据操作。报各种错误,最常见的,比如:集合已修改,无法进行枚举操作。---弃用
2)Using模式(短连接)。这种模式适合一些对于外键,导航属性不经常使用的场合,由于导航属性是放在上下文缓存中的,一旦上下文释放掉,导航属性就为null。当然,也尝试了其他大神的做法,比如,在上下文释放之前转换为 ToList或者使用饥饿加载的方式(ps:这种方式很不灵活,你总不可能遇到一个类类型就去利用反射加载找到它具有的导航属性吧或者直接InCluding),这些方法依旧没有办法解决目前的困境。也尝试这直接赋值给一个定义的同类 型的变量,但是对于这种带导航的导航的复杂类的深拷贝,没有找到合适的路子,有知道的可以告诉我,非常感谢!
以上两种方式及网上寻找的其他方式都没有解决我的问题。这里先上一下之前的Repository:
1 using System.Data.Entity; 2 using System.Data.Entity.Validation; 3 4 namespace MM.Data.Library.Entityframework 5 { 6 public class EntityFrameworkRepositoryContext : RepositoryContext, IEntityFrameworkRepositoryContext 7 { 8 protected DbContext container; 9 10 public EntityFrameworkRepositoryContext(DbContext container) 11 { 12 this.container = container; 13 } 14 15 public override void RegisterNew<TAggregateRoot>(TAggregateRoot obj) 16 { 17 this.container.Set<TAggregateRoot>().Add(obj); 18 this.IsCommit = false; 19 } 20 21 public override void RegisterModified<TAggregateRoot>(TAggregateRoot obj) 22 { 23 if (this.container.Entry<TAggregateRoot>(obj).State == EntityState.Detached) 24 { 25 this.container.Set<TAggregateRoot>().Attach(obj); 26 } 27 this.container.Entry<TAggregateRoot>(obj).State = EntityState.Modified; 28 this.IsCommit = false; 29 } 30 31 public override void RegisterDeleted<TAggregateRoot>(TAggregateRoot obj) 32 { 33 this.container.Set<TAggregateRoot>().Remove(obj); 34 this.IsCommit = false; 35 } 36 37 public override void Rollback() 38 { 39 this.IsCommit = false; 40 } 41 42 protected override void DoCommit() 43 { 44 if (!IsCommit) 45 { 46 //var count = container.SaveChanges(); 47 //IsCommit = true; 48 try 49 { 50 var count = container.SaveChanges(); 51 IsCommit = true; 52 } 53 catch (DbEntityValidationException dbEx) 54 { 55 foreach (var validationErrors in dbEx.EntityValidationErrors) 56 { 57 foreach (var validationError in validationErrors.ValidationErrors) 58 { 59 } 60 } 61 IsCommit = false; 62 } 63 } 64 } 65 66 public System.Data.Entity.DbContext DbContext 67 { 68 get { return container; } 69 } 70 71 public override void Dispose() 72 { 73 if (container != null) 74 container.Dispose(); 75 } 76 } 77 }
2.设计思路及方法
从上下文的单例模式来看,所要解决的问题无非就是在多线程对数据库写操作上面。只要在这上面做手脚,问题应该就能引刃而解。我的想法是将所有的要修改的数据分别放入UpdateList,InsertList,DeleteList三个集合中去,然后提交到数据库保存。至于DbContext的管理,通过一个数据库工厂获取,保证每一个数据库的连接都是唯一的,不重复的(防止发生类似这种错误:正在创建模型,此时不可使用上下文。),用的时候直接去Factory拿。等到数据库提交成功后,清空集合数据。看起来,实现起来很容易,但是因为还涉及到其他技术,比如Redis。所以实现过程费劲。也许我的能力还差很多。总之,废话不多说,直接上部分实现代码:
数据库上下文建立工厂:
1 /// <summary> 2 /// 数据库建立工厂 3 /// Modify By: 4 /// Modify Date: 5 /// Modify Reason: 6 /// </summary> 7 public sealed class DbFactory 8 { 9 public static IDbContext GetCurrentDbContext(string connectstring,string threadName) 10 { 11 lock (threadName) 12 { 13 //CallContext:是线程内部唯一的独用的数据槽(一块内存空间) 14 //传递Context进去获取实例的信息,在这里进行强制转换。 15 var Context = CallContext.GetData("Context") as IDbContext; 16 17 if (Context == null) //线程在内存中没有此上下文 18 { 19 var Scope = UnitoonIotContainer.Container.BeginLifetimeScope(); 20 //如果不存在上下文 创建一个(自定义)EF上下文 并且放在数据内存中去 21 Context = Scope.Resolve<IDbContext>(new NamedParameter("connectionString", connectstring)); 22 CallContext.SetData("Context", Context); 23 } 24 else 25 { 26 27 if (!Context.ConnectionString.Equals(connectstring)) 28 { 29 var Scope = UnitoonIotContainer.Container.BeginLifetimeScope(); 30 //如果不存在上下文 创建一个(自定义)EF上下文 并且放在数据内存中去 31 Context = Scope.Resolve<IDbContext>(new NamedParameter("connectionString", connectstring)); 32 CallContext.SetData("Context", Context); 33 } 34 } 35 return Context; 36 } 37 } 38 39 }
Repository:
1 public class RepositoryBase<T, TContext> : IRepositoryBase<T> where T : BaseEntity 2 3 where TContext : ContextBase, IDbContext, IDisposable, new() 4 { 5 public List<T> InsertList { get; set; } 6 public List<T> DeleteList { get; set; } 7 public List<T> UpdateList { get; set; } 8 9 #region field 10 11 protected readonly string Connectstring; 12 ///// <summary> 13 ///// </summary> 14 //protected static IDbContext Context; 15 protected IDbContext dbContext; 16 private static readonly ILifetimeScope Scope; 17 public static int xcount = 0; 18 /////// <summary> 19 /////// </summary> 20 //protected readonly DbSet<T> Dbset; 21 22 #endregion 23 24 #region ctor 25 26 static RepositoryBase() 27 { 28 Scope = UnitoonIotContainer.Container.BeginLifetimeScope(); 29 } 30 31 /// <summary> 32 /// 使用默认连接字符串name=connName 33 /// </summary> 34 public RepositoryBase() : this("") 35 { 36 } 37 38 /// <summary> 39 /// 构造函数 40 /// </summary> 41 /// <param name="connectionString">连接字符串</param> 42 public RepositoryBase(string connectionString) 43 { 44 InsertList = new List<T>(); 45 DeleteList = new List<T>(); 46 UpdateList = new List<T>(); 47 48 49 //*****做以下调整,初始化,建立所有数据库连接,保持长连接状态,在用的时候去判断使用连接 50 51 52 //todo 待处理 53 54 55 if (string.IsNullOrWhiteSpace(connectionString)) 56 { 57 var name = DataBase.GetConnectionString(Activator.CreateInstance<T>().DbType); 58 //Context= ContextHelper.GetDbContext(Activator.CreateInstance<T>().DbType); 59 connectionString = name; 60 } 61 Connectstring = connectionString; 62 63 // Context = Scope.Resolve<IDbContext>(new NamedParameter("connectionString", Connectstring)); 64 65 //Context = new TContext { ConnectionString = connectionString }; 66 67 68 // Dbset = Context.Set<T>(); 69 70 //var loggerFactory = ((DbContext)Context).GetService<ILoggerFactory>(); 71 //loggerFactory.AddProvider(new DbLoggerProvider(Console.WriteLine)); 72 //loggerFactory.AddConsole(minLevel: LogLevel.Warning); 73 } 74 75 //public RepositoryBase(TContext context) 76 //{ 77 // Context = context; 78 // Dbset = context.Set<T>(); 79 //} 80 81 #endregion 82 83 #region Method 84 85 //public virtual IDbContext GetDbContext(ILifetimeScope scope) 86 //{ 87 88 //} 89 90 #region Check Model 91 92 /// <summary> 93 /// 校正Model 94 /// </summary> 95 protected virtual void ValidModel() 96 { 97 } 98 99 #endregion 100 101 #region Update 102 103 public virtual void Update(T entity) 104 { 105 Check.NotNull(entity, "entity"); 106 UpdateList.Add(entity); 107 //context.Set<T>().Update(entity); 108 } 109 110 public virtual void Update(IEnumerable<T> entities) 111 { 112 Check.NotNull(entities, "entities"); 113 UpdateList.AddRange(entities); 114 } 115 116 #endregion 117 118 #region PageList 119 120 public virtual IEnumerable<T> GetPageList(Expression<Func<T, bool>> where, Expression<Func<T, object>> orderBy, 121 int pageIndex, int pageSize) 122 { 123 //todo 124 } 125 126 #endregion 127 128 #region Insert 129 130 public virtual void Add(T entity) 131 { 132 Check.NotNull(entity, "entity"); 133 //排除已经存在的项(对于多线程没有任何用处) 134 if (!InsertList.Exists(e => e.Equals(entity))) 135 { 136 InsertList.Add(entity); 137 } 138 139 } 140 141 public virtual void Add(IEnumerable<T> entities) 142 { 143 Check.NotNull(entities, "entities"); 144 InsertList.AddRange(entities); 145 } 146 147 public void BulkInsert(IEnumerable<T> entities) 148 { 149 Check.NotNull(entities, "entities"); 150 InsertList.AddRange(entities); 151 } 152 153 #endregion 154 155 #region Delete 156 157 public virtual void Delete(int id) 158 { 159 var entity = GetById(id); 160 Delete(entity); 161 // throw new NotImplementedException("Delete(int id)"); 162 } 163 164 public virtual void Delete(string id) 165 { 166 throw new NotImplementedException("Delete(string id)"); 167 } 168 169 public virtual void Delete(T entity) 170 { 171 Check.NotNull(entity, "entity"); 172 DeleteList.Add(entity); 173 } 174 175 public virtual void Delete(IEnumerable<T> entities) 176 { 177 Check.NotNull(entities, "entities"); 178 foreach (var x1 in DeleteList) 179 { 180 DeleteList.Add(x1); 181 } 182 } 183 184 public virtual void Delete(Expression<Func<T, bool>> where) 185 { 186 var list = DeleteList.Where(where.Compile()); 187 Delete(list); 188 } 189 190 #endregion 191 192 #region Commit 193 194 public int Commit() 195 { 196 ValidModel(); 197 //var x = Activator.CreateInstance<T>(); 198 //Context = ContextHelper.GetDbContext(x.DbType); 199 //using (var scope = UnitoonIotContainer.Container.BeginLifetimeScope()) 200 //{ 201 // var context = scope.Resolve<IDbContext>(new NamedParameter("connectionString", Connectstring)); 202 203 //var loggerFactory = Activator.CreateInstance<ILoggerFactory>();// ((DbContext)context).GetService<ILoggerFactory>(); 204 //loggerFactory.AddProvider(new DbLoggerProvider(Console.WriteLine)); 205 dbContext = DbFactory.GetCurrentDbContext(Connectstring, Thread.CurrentThread.Name); 206 var dbset = dbContext.Set<T>(); 207 if (InsertList != null && InsertList.Any()) 208 { 209 List<T> InsertNewList = InsertList.Distinct(new PropertyComparer<T>("Id")).ToList();//按照特定规则排除重复项 210 dbset.AddRange(InsertNewList); 211 } 212 213 if (DeleteList != null && DeleteList.Any()) 214 DeleteList.ForEach(t => 215 { 216 // Context.Entry(t).State = EntityState.Detached;//将之前上下文跟踪的状态丢弃 217 //dbContext.Entry(t).State = EntityState.Detached;//将之前上下文跟踪的状态丢弃 218 dbset.Attach(t); 219 dbset.Remove(t); 220 }); 221 if (UpdateList != null && UpdateList.Any()) 222 { 223 List<T> UpdateNewList = UpdateList.Distinct(new PropertyComparer<T>("Id")).ToList();//按照特定规则排除重复项 224 UpdateNewList.ForEach(t => 225 { 226 //Context.Entry(t).State = EntityState.Detached;//将之前上下文跟踪的状态丢弃 227 // dbContext.Entry(t).State = EntityState.Detached;//将之前上下文跟踪的状态丢弃 228 dbContext.Entry(t).State = EntityState.Modified; 229 });//.UpdateRange(UpdateNewList); 230 } 231 var result = 0; 232 try 233 { 234 result = dbContext.SaveChanges(); 235 } 236 catch (Exception ex) 237 { 238 239 // throw; 240 } 241 242 if (InsertList != null && InsertList.Any()) 243 InsertList.Clear(); 244 if (DeleteList != null && DeleteList.Any()) 245 DeleteList.Clear(); 246 if (UpdateList != null && UpdateList.Any()) 247 UpdateList.Clear(); 248 return result; 249 //} 250 } 251 252 public async Task<int> CommitAsync() 253 { 254 ValidModel(); 255 //todo 256 257 } 258 259 #endregion 260 261 #region Query 262 public IQueryable<T> Get() 263 { 264 return GetAll().AsQueryable(); 265 } 266 //public virtual T Get(Expression<Func<T, bool>> @where) 267 //{ 268 // using (var scope = UnitoonIotContainer.Container.BeginLifetimeScope()) 269 // { 270 271 // var context = scope.Resolve<IDbContext>(new NamedParameter("connectionString", Connectstring)); 272 // var dbset = context.Set<T>(); 273 // return dbset.FirstOrDefault(where); 274 // } 275 //} 276 277 public virtual async Task<T> GetAsync(Expression<Func<T, bool>> @where) 278 { 279 //todo 280 } 281 282 public virtual T GetById(int id) 283 { 284 throw new NotImplementedException("GetById(int id)"); 285 } 286 287 public virtual async Task<T> GetByIdAsync(int id) 288 { 289 throw new NotImplementedException("GetById(int id)"); 290 } 291 292 public virtual T GetById(string id) 293 { 294 throw new NotImplementedException("GetById(int id)"); 295 } 296 297 public virtual async Task<T> GetByIdAsync(string id) 298 { 299 throw new NotImplementedException("GetById(int id)"); 300 } 301 302 public virtual T Get(Expression<Func<T, bool>> @where)//, params string[] includeProperties 303 { 304 //var scope = UnitoonIotContainer.Container.BeginLifetimeScope(); 305 //{ 306 // var context = scope.Resolve<IDbContext>(new NamedParameter("connectionString", Connectstring)); 307 //Thread.Sleep(50); 308 //lock (Context) 309 { 310 dbContext= DbFactory.GetCurrentDbContext(Connectstring, Thread.CurrentThread.Name); 311 var dbset = dbContext.Set<T>().Where(e => !e.IsDeleted).AsQueryable(); 312 var entity = dbset.FirstOrDefault(where); 313 //test 314 // Context.Entry(entity).State = EntityState.Detached;//将之前上下文跟踪的状态丢弃 315 return entity; 316 } 317 318 319 //} 320 } 321 322 public virtual IEnumerable<T> GetAll() 323 { 324 //Thread.Sleep(50); 325 //lock (Context) 326 { 327 dbContext = DbFactory.GetCurrentDbContext(Connectstring, Thread.CurrentThread.Name); 328 var dbset = dbContext.Set<T>().Where(e => !e.IsDeleted); 329 //test 330 //dbset.ToList().ForEach(t => 331 //{ 332 // Context.Entry(t).State = EntityState.Detached;//将之前上下文跟踪的状态丢弃 333 //}); 334 335 return dbset; 336 } 337 338 339 340 341 //var scope = UnitoonIotContainer.Container.BeginLifetimeScope(); 342 343 // var context = scope.Resolve<IDbContext>(new NamedParameter("connectionString", Connectstring)); 344 345 } 346 347 public async virtual Task<IEnumerable<T>> GetAllAsync() 348 { 349 //todo 350 } 351 352 public virtual IEnumerable<T> GetMany(Expression<Func<T, bool>> where) 353 { 354 //using (var scope = UnitoonIotContainer.Container.BeginLifetimeScope()) 355 //{ 356 // var context = scope.Resolve<IDbContext>(new NamedParameter("connectionString", Connectstring)); 357 358 // var dbset = context.Set<T>(); 359 //Thread.Sleep(50); 360 //lock (Context) 361 { 362 dbContext = DbFactory.GetCurrentDbContext(Connectstring, Thread.CurrentThread.Name); 363 var dbset = dbContext.Set<T>().Where(e => !e.IsDeleted); 364 //test 365 //dbset.ToList().ForEach(t => 366 //{ 367 // Context.Entry(t).State = EntityState.Detached;//将之前上下文跟踪的状态丢弃 368 //}); 369 return dbset.Where(@where).ToList(); 370 } 371 372 //} 373 } 374 375 public virtual async Task<IEnumerable<T>> GetManyAsync(Expression<Func<T, bool>> where) 376 { 377 //todo 378 } 379 380 public virtual IEnumerable<T> IncludeSubSets(params Expression<Func<T, object>>[] includeProperties) 381 { 382 //todo 383 } 384 385 #region navigation 386 /// <summary> 387 /// 加载导航 388 /// </summary> 389 /// <param name="where"></param> 390 /// <param name="includeProperties"></param> 391 /// <returns></returns> 392 //public virtual T Get(Expression<Func<T, bool>> @where, params Expression<Func<T, object>>[] includeProperties) 393 //{ 394 // using (var scope = UnitoonIotContainer.Container.BeginLifetimeScope()) 395 // { 396 397 // var context = scope.Resolve<IDbContext>(new NamedParameter("connectionString", Connectstring)); 398 // var dbset = context.Set<T>(); 399 // var query = includeProperties.Aggregate<Expression<Func<T, object>>, IQueryable<T>>(dbset, 400 // (current, includeProperty) => current.Include(includeProperty)); 401 // return query.FirstOrDefault(where); 402 // } 403 //} 404 405 //public virtual T Get(Expression<Func<T, bool>> @where)//, params string[] includeProperties 406 //{ 407 // //反射获取导航 408 // var includeProperties = 409 // Activator.CreateInstance<T>().GetType().GetProperties().Where(p => p.GetMethod.IsVirtual).Select(e => e.Name).ToArray(); 410 411 //todo 412 //} 413 #endregion 414 public List<TDynamicEntity> GetDynamic<TTable, TDynamicEntity>(Expression<Func<TTable, object>> selector, 415 Func<object, TDynamicEntity> maker) where TTable : class 416 { 417 //todo 418 } 419 420 public List<TDynamicEntity> GetDynamic<TTable, TDynamicEntity>(Func<TTable, object> selector, 421 Func<object, TDynamicEntity> maker) where TTable : class 422 { 423 //todo 424 425 } 426 427 #endregion 428 429 #region Count 430 431 public virtual async Task<int> CountAsync() 432 { 433 //todo 434 } 435 436 public virtual async Task<int> CountByAsync(Expression<Func<T, bool>> where) 437 { 438 //todo 439 } 440 441 #endregion 442 443 #region Exists 444 445 public virtual bool Exists(string id) 446 { 447 throw new NotImplementedException(); 448 } 449 450 public virtual bool Exists(int id) 451 { 452 throw new NotImplementedException(); 453 } 454 455 public virtual async Task<bool> ExistsAsync(string id) 456 { 457 throw new NotImplementedException(); 458 } 459 460 public virtual async Task<bool> ExistsAsync(int id) 461 { 462 throw new NotImplementedException(); 463 } 464 465 public virtual bool Exists(Expression<Func<T, bool>> @where) 466 { 467 throw new NotImplementedException(); 468 } 469 470 public virtual async Task<bool> ExistsAsync(Expression<Func<T, bool>> @where) 471 { 472 throw new NotImplementedException(); 473 } 474 475 #endregion 476 477 478 #endregion 479 }
以上就是EF6的多线程与分库架构设计实现的部分相关内容。
如果有需要全部源代码或者交流的,直接联系我QQ:694666781 或者: https://item.taobao.com/item.htm?spm=a2oq0.12575281.0.0.3d2a1debWgF2RT&ft=t&id=608713437692
另外,该场景下,Redis相关使用方法及可能遇到的问题及解决方法我会另写一篇进行展开。如有不妥不正之处,请大神指正,谢谢!