EF通用数据层封装类(支持读写分离,一主多从)
淺談orm
  記得四年前在學(xué)校第一次接觸到 Ling to Sql,那時候瞬間發(fā)現(xiàn)不用手寫sql語句是多么的方便,后面慢慢的接觸了許多orm框架,像 EF,Dapper,Hibernate,ServiceStack.OrmLite?等。當(dāng)然每種orm都有各自的優(yōu)勢,也有不足的地方。園子里也有很多大神開源了他們寫的orm,如SqlSugar,Chloe.ORM,CYQ.Data 等。先不說這些開源的orm使用度怎么樣,我覺得起碼從開源的精神上就很可嘉了,我也曾下載過這幾位大神的源碼進行學(xué)習(xí)。
  所有orm最終體現(xiàn)的一點就是方便,減少程序員重復(fù)性的工作,當(dāng)然目前還有一些公司還是使用手寫sql的方式來做,我覺得整個項目都使用手寫sql來做真的是有點閑到蛋疼,并不是不推薦手寫sql的方式,只是個人覺得最基本的增刪改查這些都手寫的話,那其實考驗不是能力,而是耐力。有人說手寫sql的方式可控性強,性能高,我想說的是orm也能做到,關(guān)鍵是你怎么去使用。
  orm的優(yōu)點非常明顯,開發(fā)便捷,但或許也是由于這個優(yōu)點,讓很多偷懶的程序員也會漸漸忘了sql語句的寫法,我遇到過很多的程序員朋友用了EF后,手寫sql,視圖、存儲過程這些都不想用了,我個人覺手寫sql這種還是必要的。不然某一天你看到別人的程序里面寫著 “exec ?xxxx”,你就會突然覺得“啊,好像在哪里見過.....”。所以我想說的是“該出手時還是得出手"。
淺談Entity Framework
Entity Framework 是微軟家的orm框架,隨著 Entity Framework 不斷的完善強化,目前相信使用的比例相對其他的orm來說還是較高的。像我目前使用的最多的就是EF和Dapper。確實,EF用起來開發(fā)過程中會方便很多,畢竟EF走過了這么年頭,無論是成熟度,還是性能等都提高了很多,也有很多開發(fā)者為EF提供了擴展功能,如entity framework extended 等。而且作為.net開發(fā)者來說項目通用性也很強,資料也多,微軟在這塊的更新力度也很給力。不過之前剛出的EF Core也存在一些坑,畢竟還是初期階段,相信現(xiàn)在后面會越來越好的。
Entity Framework ?提供了三種開發(fā)模式,code first,db first,model first。目前用的最多的就屬code first了。至于這三種模式的簡單使用和區(qū)別,大家可以參考下這篇文章。
我曾聽一些朋友說過說EF使用起來性能很差,生成的sql語句很難看等。我覺得說這種話之前還是先檢查下代碼或者多看下一些EF文章吧,要先確保自己沒給自己挖坑,然后才能指責(zé)別人的不好。如果真心覺得EF或者其他的orm用起來很不爽,那就自己寫一個吧,我也曾經(jīng)和同事用Dapper擴展一個通用的orm,當(dāng)時是出于一種學(xué)習(xí)和使用方便的角度。
Entity Framework 通用數(shù)據(jù)層類
這里提供下 EF 通用數(shù)據(jù)層父類方法,其實網(wǎng)上也有很多人提供了自己項目中的 EF 通用數(shù)據(jù)層父類方法,所以這里提供的并不是最優(yōu)和最好的選擇,只能說是可以通用的類,方便大家學(xué)習(xí)和使用,具體代碼如下:
DbContextFactory?DbContext工廠類
public class DbContextFactory{ public DbContext GetDbContext(){ string key = typeof(DBContext.DbContextFactory).Name + "XJHDbContext";DbContext dbContext = CallContext.GetData(key) as DbContext;if (dbContext == null){dbContext = new XJHDbContext();CallContext.SetData(key, dbContext);} return dbContext;}}
擴展類,實現(xiàn)讀寫分離
上面的通用類是比較基礎(chǔ)簡單通用的,適合于單庫讀寫操作。對于EF實現(xiàn)讀寫分離,之前網(wǎng)上找過類似的參考文章,很多人文章都是使用?DbCommandInterceptor攔截器?來實現(xiàn),具體的做法是通過攔截到sql語句,然后根據(jù)具體條件去判斷是走主庫還是從庫。這種做法不是不行,只是個人感覺不是很好擴展,而且要在攔截器里面做限制判斷。
其實說白了EF本身就是一個讀寫分離的orm。用過EF的人知道,EF提供訪問數(shù)據(jù)庫的是 DbContext 這個對象,所以想實現(xiàn)讀寫分離的就很簡單了,只要在程序中使用兩個不同的DbContext對象,一個負責(zé)讀,一個負責(zé)寫就好了。
所以在上面提供的通用封裝類中稍微做下修改,修改如下DbContextFactory中獲取DbContext的方法,實現(xiàn)一個讀的DbContext和一個寫的DbContext對象的獲取。
  這里要注意下,對于讀的DbContext來說,要做下設(shè)置
  1.使用 Database.SetInitializer(new NullDatabaseInitializer<ReadDbContext>()); 改變ef初始化策略,因為一般我們使用讀寫分離的話從庫都是同步于主庫的,所以不使用ef的自動創(chuàng)建數(shù)據(jù)庫策略。
  2.重寫?SaveChanges?方法,對應(yīng)從庫來說,只提供讀取的功能,所以防止誤操作,這里禁用掉SaveChanges方法,一般需要使用從讀的保存方法,就對外拋出異常。
代碼如下:
支持讀寫分離的 DbContextFactory 類
public class DbContextFactory{ public DbContext GetWriteDbContext(){ string key = typeof(DbContextFactory).Name + "WriteDbContext";DbContext dbContext = CallContext.GetData(key) as DbContext;if (dbContext == null){dbContext = new WriteDbContext();CallContext.SetData(key, dbContext);} return dbContext;} public DbContext GetReadDbContext(){ string key = typeof(DbContextFactory).Name + "ReadDbContext";DbContext dbContext = CallContext.GetData(key) as DbContext;
if (dbContext == null){dbContext = new ReadDbContext();CallContext.SetData(key, dbContext);} return dbContext;}}
? ?對應(yīng)的?DbBase?類也做下修改,主要將上面的Db對象改作?MasterDb?和 SlaveDb 對象,并且把上面的讀寫方法坐下調(diào)整,修改后如下:
支持讀寫分離的?DbBase類
public class DbBase{ //是否讀寫分離(可以配置在配置文件中)private static readonly bool IsReadWriteSeparation = true;#region EF上下文對象(主庫) protected DbContext MasterDb => _masterDb.Value;
private readonly Lazy<DbContext> _masterDb = new Lazy<DbContext>(() =>
new DbContextFactory().GetWriteDbContext());
#endregion EF上下文對象(主庫)
#region EF上下文對象(從庫)
protected DbContext SlaveDb => IsReadWriteSeparation ? _slaveDb.Value : _masterDb.Value; private readonly Lazy<DbContext> _slaveDb = new Lazy<DbContext>(() => new DbContextFactory().GetReadDbContext()); #endregion EF上下文對象(從庫) #region 自定義其他方法 /// <summary>/// 執(zhí)行存儲過程或自定義sql語句--返回集合(自定義返回類型) ? ? ? ?/// </summary>/// <param name="sql"></param>/// <param name="parms"></param>/// <param name="cmdType"></param>/// <returns></returns>public List<TModel> Query<TModel>(string sql, List<SqlParameter> parms, CommandType cmdType = CommandType.Text){ //存儲過程(exec getActionUrlId @name,@ID)if (cmdType == CommandType.StoredProcedure){StringBuilder paraNames = new StringBuilder();
foreach (var sqlPara in parms){paraNames.Append($" @{sqlPara},");}sql = paraNames.Length > 0 ? $"exec {sql} {paraNames.ToString().Trim(',')}" : $"exec {sql} ";} var list = SlaveDb.Database.SqlQuery<TModel>(sql, parms.ToArray()); var enityList = list.ToList(); return enityList;} /// <summary>/// 自定義語句和存儲過程的增刪改--返回影響的行數(shù) ? ? ? ?/// </summary>/// <param name="sql"></param>/// <param name="parms"></param>/// <param name="cmdType"></param>/// <returns></returns>public int Execute(string sql, List<SqlParameter> parms, CommandType cmdType = CommandType.Text){ //存儲過程(exec getActionUrlId @name,@ID)if (cmdType == CommandType.StoredProcedure){StringBuilder paraNames = new StringBuilder(); foreach (var sqlPara in parms){paraNames.Append($" @{sqlPara},");}sql = paraNames.Length > 0 ?$"exec {sql} {paraNames.ToString().Trim(',')}" :$"exec {sql} ";} int ret = MasterDb.Database.ExecuteSqlCommand(sql, parms.ToArray()); return ret;} #endregion 自定義其他方法} /// <summary>/// mssql數(shù)據(jù)庫 數(shù)據(jù)層 父類 ? ?/// </summary>/// <typeparam name="T"></typeparam>public class DbBase<T> : DbBase where T : class, new(){ #region INSERT /// <summary>/// 新增 實體 ? ? ? ?/// </summary>/// <param name="model"></param>/// <returns></returns>public void Insert(T model){MasterDb.Set<T>().Add(model);} /// <summary>/// 普通批量插入 ? ? ? ?/// </summary>/// <param name="datas"></param>public void InsertRange(List<T> datas){MasterDb.Set<T>().AddRange(datas);} #endregion INSERT #region DELETE /// <summary>/// 根據(jù)模型刪除 ? ? ? ?/// </summary>/// <param name="model">包含要刪除id的對象</param>/// <returns></returns>public void Delete(T model){MasterDb.Set<T>().Attach(model);MasterDb.Set<T>().Remove(model);} /// <summary>/// 刪除 ? ? ? ?/// </summary>/// <param name="whereLambda"></param>public void Delete(Expression<Func<T, bool>> whereLambda){MasterDb.Set<T>().Where(whereLambda).Delete();} #endregion DELETE #region UPDATE /// <summary>/// 單個對象指定列修改 ? ? ? ?/// </summary>/// <param name="model">要修改的實體對象</param>/// <param name="proNames">要修改的 屬性 名稱</param>/// <param name="isProUpdate"></param>/// <returns></returns>public void Update(T model, List<string> proNames, bool isProUpdate = true){ //將 對象 添加到 EF中MasterDb.Set<T>().Attach(model); var setEntry = ((IObjectContextAdapter)MasterDb).ObjectContext.ObjectStateManager.GetObjectStateEntry(model); //指定列修改if (isProUpdate){ foreach (string proName in proNames){setEntry.SetModifiedProperty(proName);}} //忽略類修改else{Type t = typeof(T);List<PropertyInfo> proInfos = t.GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList(); foreach (var item in proInfos){ string proName = item.Name; if (proNames.Contains(proName)){ continue;}setEntry.SetModifiedProperty(proName);}}} /// <summary>/// 單個對象修改 ? ? ? ?/// </summary>/// <param name="model"></param>/// <returns></returns>public void Update(T model){DbEntityEntry entry = MasterDb.Entry<T>(model);MasterDb.Set<T>().Attach(model);entry.State = EntityState.Modified;} /// <summary>/// 批量修改 ? ? ? ?/// </summary>/// <param name="whereLambda"></param>/// <param name="updateExpression"></param>public void Update(Expression<Func<T, bool>> whereLambda, Expression<Func<T, T>> updateExpression){MasterDb.Set<T>().Where(whereLambda).Update(updateExpression);} /// <summary>/// 批量修改 ? ? ? ?/// </summary>/// <param name="models"></param>/// <returns></returns>public void UpdateAll(List<T> models){ foreach (var model in models){DbEntityEntry entry = MasterDb.Entry(model);entry.State = EntityState.Modified;}} /// <summary>/// 批量統(tǒng)一修改 ? ? ? ?/// </summary>/// <param name="model">要修改的實體對象</param>/// <param name="whereLambda">查詢條件</param>/// <param name="modifiedProNames"></param>/// <returns></returns>public void Update(T model, Expression<Func<T, bool>> whereLambda,
params string[] modifiedProNames){ //查詢要修改的數(shù)據(jù)List<T> listModifing = MasterDb.Set<T>().Where(whereLambda).ToList();Type t = typeof(T);List<PropertyInfo> proInfos = t.GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList();Dictionary<string, PropertyInfo> dictPros = new Dictionary<string, PropertyInfo>();proInfos.ForEach(p =>{ if (modifiedProNames.Contains(p.Name)){dictPros.Add(p.Name, p);}}); if (dictPros.Count <= 0){ throw new Exception("指定修改的字段名稱有誤或為空");} foreach (var item in dictPros){PropertyInfo proInfo = item.Value; //取出 要修改的值object newValue = proInfo.GetValue(model, null); //批量設(shè)置 要修改 對象的 屬性foreach (T oModel in listModifing){ //為 要修改的對象 的 要修改的屬性 設(shè)置新的值proInfo.SetValue(oModel, newValue, null);}}} #endregion UPDATE #region SELECT /// <summary>/// 根據(jù)主鍵查詢 ? ? ? ?/// </summary>/// <param name="id"></param>/// <returns></returns>public T FindById(dynamic id){ return SlaveDb.Set<T>().Find(id);} /// <summary>/// 獲取默認一條數(shù)據(jù),沒有則為NULL ? ? ? ?/// </summary>/// <param name="whereLambda"></param>/// <returns></returns>public T FirstOrDefault(Expression<Func<T, bool>> whereLambda = null){ if (whereLambda == null){ return SlaveDb.Set<T>().FirstOrDefault();} return SlaveDb.Set<T>().FirstOrDefault(whereLambda);} /// <summary>/// 獲取全部數(shù)據(jù) ? ? ? ?/// </summary>/// <returns></returns>public List<T> GetAll(string ordering = null){ return ordering == null? SlaveDb.Set<T>().ToList(): SlaveDb.Set<T>().OrderBy(ordering).ToList();} /// <summary>/// 帶條件查詢獲取數(shù)據(jù) ? ? ? ?/// </summary>/// <param name="whereLambda"></param>/// <param name="ordering"></param>/// <returns></returns>public List<T> GetAll(Expression<Func<T, bool>> whereLambda, string ordering = null){ var iQueryable = SlaveDb.Set<T>().Where(whereLambda);
return ordering == null? iQueryable.ToList(): iQueryable.OrderBy(ordering).ToList();} /// <summary>/// 帶條件查詢獲取數(shù)據(jù) ? ? ? ?/// </summary>/// <param name="whereLambda"></param>/// <returns></returns>public IQueryable<T> GetAllIQueryable(Expression<Func<T, bool>> whereLambda = null){ return whereLambda == null ? SlaveDb.Set<T>() : SlaveDb.Set<T>().Where(whereLambda);} /// <summary>/// 獲取數(shù)量 ? ? ? ?/// </summary>/// <param name="whereLambd"></param>/// <returns></returns>public int GetCount(Expression<Func<T, bool>> whereLambd = null){
return whereLambd == null ? SlaveDb.Set<T>().Count()
: SlaveDb.Set<T>().Where(whereLambd).Count();} /// <summary>/// 判斷對象是否存在 ? ? ? ?/// </summary>/// <param name="whereLambd"></param>/// <returns></returns>public bool Any(Expression<Func<T, bool>> whereLambd){ return SlaveDb.Set<T>().Where(whereLambd).Any();} /// <summary>/// 分頁查詢 ? ? ? ?/// </summary>/// <param name="pageIndex">當(dāng)前頁碼</param>/// <param name="pageSize">每頁大小</param>/// <param name="rows">總條數(shù)</param>/// <param name="orderBy">排序條件(一定要有)</param>/// <param name="whereLambda">查詢添加(可有,可無)</param>/// <param name="isOrder">是否是Order排序</param>/// <returns></returns>public List<T> Page<TKey>(int pageIndex, int pageSize, out int rows,
Expression<Func<T, TKey>> orderBy, Expression<Func<T, bool>> whereLambda = null,
bool isOrder = true){IQueryable<T> data = isOrder ?SlaveDb.Set<T>().OrderBy(orderBy) :SlaveDb.Set<T>().OrderByDescending(orderBy);
if (whereLambda != null){data = data.Where(whereLambda);}rows = data.Count();
return data.PageBy((pageIndex - 1) * pageSize, pageSize).ToList();} /// <summary>/// 分頁查詢 ? ? ? ?/// </summary>/// <param name="pageIndex">當(dāng)前頁碼</param>/// <param name="pageSize">每頁大小</param>/// <param name="rows">總條數(shù)</param>/// <param name="ordering">排序條件(一定要有)</param>/// <param name="whereLambda">查詢添加(可有,可無)</param>/// <returns></returns>public List<T> Page(int pageIndex, int pageSize, out int rows,
string ordering, Expression<Func<T, bool>> whereLambda = null){ // 分頁 一定注意: Skip 之前一定要 OrderByvar data = SlaveDb.Set<T>().OrderBy(ordering);
if (whereLambda != null){data = data.Where(whereLambda);}rows = data.Count();
return data.PageBy((pageIndex - 1) * pageSize, pageSize).ToList();} /// <summary>/// 查詢轉(zhuǎn)換 ? ? ? ?/// </summary>/// <typeparam name="TDto"></typeparam>/// <param name="whereLambda"></param>/// <returns></returns>public List<TDto> Select<TDto>(Expression<Func<T, bool>> whereLambda){ return SlaveDb.Set<T>().Where(whereLambda).Select<TDto>().ToList();} #endregion SELECT #region ORTHER /// <summary>/// 執(zhí)行存儲過程或自定義sql語句--返回集合 ? ? ? ?/// </summary>/// <param name="sql"></param>/// <param name="parms"></param>/// <param name="cmdType"></param>/// <returns></returns>public List<T> Query(string sql, List<SqlParameter> parms, CommandType cmdType = CommandType.Text){ return Query<T>(sql, parms, cmdType);} /// <summary>/// 提交保存 ? ? ? ?/// </summary>/// <returns></returns>public int SaveChanges(){ return MasterDb.SaveChanges();} /// <summary>/// 回滾 ? ? ? ?/// </summary>public void RollBackChanges(){ var items = MasterDb.ChangeTracker.Entries().ToList();items.ForEach(o => o.State = EntityState.Unchanged);} #endregion ORTHER}
這樣簡單的讀寫分離就實現(xiàn)了,實現(xiàn)邏輯也比較清晰,方便擴展。
進一步改造,實現(xiàn)多從庫讀取
  一般做讀寫分離,都會做一主多從,特別對讀取量比較大的項目,這樣多庫讀取就能減輕讀庫的壓力。所以對于上面的方法,做下改造。
  上面可以看到,主庫和從庫都是通過 DbContextFactory 這個類來獲取的,在GetReadDbContext 方法中每次都是獲取 ReadDbContext 這個對象。那么對于多個從庫的情況下,每次讀取到底要去哪個庫讀取數(shù)據(jù)呢?這里就是一個算法規(guī)則的問題了,或者說是策略吧,如果使用過nginx的朋友就知道,nginx本身內(nèi)部在實現(xiàn)負載均衡的時候提供了多種策略,比如輪詢,加權(quán)輪詢,ip_hash等策略。其實上面獲取同一個ReadDbContext 的方法也算一種策略,叫單一策略,每次都獲取單一的對象。
多從庫的情況下,我們簡單的來實現(xiàn)另一種獲取策略,隨機策略,每次都隨機獲取到一個從庫的對象,這種是最簡單的策略,當(dāng)然,正式使用的話大家可以發(fā)揮自己的創(chuàng)造力,寫出多了的算法策略。
首先,定義一個策略接口,方便策略的擴展和切換,代碼如下:
IReadDbStrategy 接口
/// <summary>/// 從數(shù)據(jù)庫獲取策略接口 ?/// </summary>public interface IReadDbStrategy{ /// <summary>/// 獲取讀庫 ? ? ?/// </summary>/// <returns></returns>DbContext GetDbContext();}單從庫情況下,定義一個單一策略,代碼如下:
單一策略
/// <summary>/// 單一策略 ? /// </summary>public class SingleStrategy : IReadDbStrategy{ public DbContext GetDbContext(){ return new ReadDbContext();}}多從庫情況下,定義一個隨機策略,代碼如下:
隨機策略
/// <summary>/// 隨機策略 ? ?/// </summary>public class RandomStrategy : IReadDbStrategy{ //所有讀庫類型public static List<Type> DbTypes; static RandomStrategy(){LoadDbs();} //加載所有的讀庫類型static void LoadDbs(){DbTypes = new List<Type>();var assembly = Assembly.GetExecutingAssembly();
var types = assembly.GetTypes();
foreach (var type in types){ if (type.BaseType == typeof(BaseReadDbContext)){DbTypes.Add(type);}}} public DbContext GetDbContext(){ int randomIndex = new Random().Next(0, DbTypes.Count);
var dbType = DbTypes[randomIndex];
var dbContext = Activator.CreateInstance(dbType) as DbContext;
return dbContext;}}
這樣,所有從庫我們都基于策略去獲取,擴展也比較方便。修改下 DbContextFactory 類的 GetReadDbContext 方法,通過策略接口來獲取,代碼如下:
支持一主多從的 DbContextFactory 類
public class DbContextFactory{ //todo:這里可以自己通過注入的方式來實現(xiàn),就會更加靈活private static readonly IReadDbStrategy ReadDbStrategy = new RandomStrategy(); ? ? ? ?public DbContext GetWriteDbContext(){ string key = typeof(DbContextFactory).Name + "WriteDbContext";DbContext dbContext = CallContext.GetData(key) as DbContext;if (dbContext == null){dbContext = new WriteDbContext();CallContext.SetData(key, dbContext);} return dbContext;} public DbContext GetReadDbContext(){ string key = typeof(DbContextFactory).Name + "ReadDbContext";DbContext dbContext = CallContext.GetData(key) as DbContext;
if (dbContext == null){dbContext = ReadDbStrategy.GetDbContext();CallContext.SetData(key, dbContext);} return dbContext;}}
這樣簡單的一主多從也實現(xiàn)了。
參考文章
  http://www.cnblogs.com/zhaopei/p/5721789.html
  http://www.cnblogs.com/GuZhenYin/p/5482288.html
回到頂部
源碼分享
所有的代碼提供給大家的更多的是一種思路和學(xué)習(xí)的參考,如果有什么不足的地方也歡迎大家批評指正,如果覺得對你有幫助,不要吝嗇你的鼠標(biāo),幫忙點個星,點個贊吧。
源碼地址: https://github.com/qq1206676756/EF_DbHelper
原文地址:http://www.cnblogs.com/qtqq/p/6942312.html
.NET社區(qū)新聞,深度好文,微信中搜索dotNET跨平臺或掃描二維碼關(guān)注
總結(jié)
以上是生活随笔為你收集整理的EF通用数据层封装类(支持读写分离,一主多从)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 编写高性能 .NET 代码 第一章:工具
- 下一篇: IdentityServer4(OAut
