浦东做营销网站,河北黄骅市网站建设,会展设计专业,建立官方网站浅谈orm 记得四年前在学校第一次接触到 Ling to Sql#xff0c;那时候瞬间发现不用手写sql语句是多么的方便#xff0c;后面慢慢的接触了许多orm框架#xff0c;像 EF#xff0c;Dapper,Hibernate,ServiceStack.OrmLite 等。当然每种orm都有各自的优势#xff0c;也有不足…浅谈orm 记得四年前在学校第一次接触到 Ling to Sql那时候瞬间发现不用手写sql语句是多么的方便后面慢慢的接触了许多orm框架像 EFDapper,Hibernate,ServiceStack.OrmLite 等。当然每种orm都有各自的优势也有不足的地方。园子里也有很多大神开源了他们写的orm,如SqlSugar,Chloe.ORM,CYQ.Data 等。先不说这些开源的orm使用度怎么样我觉得起码从开源的精神上就很可嘉了我也曾下载过这几位大神的源码进行学习。 所有orm最终体现的一点就是方便减少程序员重复性的工作当然目前还有一些公司还是使用手写sql的方式来做我觉得整个项目都使用手写sql来做真的是有点闲到蛋疼并不是不推荐手写sql的方式只是个人觉得最基本的增删改查这些都手写的话那其实考验不是能力而是耐力。有人说手写sql的方式可控性强性能高我想说的是orm也能做到关键是你怎么去使用。 orm的优点非常明显开发便捷但或许也是由于这个优点让很多偷懒的程序员也会渐渐忘了sql语句的写法我遇到过很多的程序员朋友用了EF后手写sql视图、存储过程这些都不想用了我个人觉手写sql这种还是必要的。不然某一天你看到别人的程序里面写着 “exec xxxx”你就会突然觉得“啊好像在哪里见过.....”。所以我想说的是“该出手时还是得出手。 浅谈Entity Framework Entity Framework 是微软家的orm框架随着 Entity Framework 不断的完善强化目前相信使用的比例相对其他的orm来说还是较高的。像我目前使用的最多的就是EF和Dapper。确实EF用起来开发过程中会方便很多毕竟EF走过了这么年头无论是成熟度还是性能等都提高了很多也有很多开发者为EF提供了扩展功能如entity framework extended 等。而且作为.net开发者来说项目通用性也很强资料也多微软在这块的更新力度也很给力。不过之前刚出的EF Core也存在一些坑毕竟还是初期阶段相信现在后面会越来越好的。 Entity Framework 提供了三种开发模式code firstdb firstmodel first。目前用的最多的就属code first了。至于这三种模式的简单使用和区别大家可以参考下这篇文章。 我曾听一些朋友说过说EF使用起来性能很差生成的sql语句很难看等。我觉得说这种话之前还是先检查下代码或者多看下一些EF文章吧要先确保自己没给自己挖坑然后才能指责别人的不好。如果真心觉得EF或者其他的orm用起来很不爽那就自己写一个吧我也曾经和同事用Dapper扩展一个通用的orm当时是出于一种学习和使用方便的角度。 Entity Framework 通用数据层类 这里提供下 EF 通用数据层父类方法其实网上也有很多人提供了自己项目中的 EF 通用数据层父类方法所以这里提供的并不是最优和最好的选择只能说是可以通用的类方便大家学习和使用具体代码如下
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;}} 扩展类实现读写分离 上面的通用类是比较基础简单通用的适合于单库读写操作。对于EF实现读写分离之前网上找过类似的参考文章很多人文章都是使用 DbCommandInterceptor拦截器 来实现具体的做法是通过拦截到sql语句然后根据具体条件去判断是走主库还是从库。这种做法不是不行只是个人感觉不是很好扩展而且要在拦截器里面做限制判断。 其实说白了EF本身就是一个读写分离的orm。用过EF的人知道EF提供访问数据库的是 DbContext 这个对象所以想实现读写分离的就很简单了只要在程序中使用两个不同的DbContext对象一个负责读一个负责写就好了。 所以在上面提供的通用封装类中稍微做下修改修改如下DbContextFactory中获取DbContext的方法实现一个读的DbContext和一个写的DbContext对象的获取。 这里要注意下对于读的DbContext来说要做下设置 1.使用 Database.SetInitializer(new NullDatabaseInitializerReadDbContext()); 改变ef初始化策略因为一般我们使用读写分离的话从库都是同步于主库的所以不使用ef的自动创建数据库策略。 2.重写 SaveChanges 方法对应从库来说只提供读取的功能所以防止误操作这里禁用掉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;}} 对应的 DbBase 类也做下修改主要将上面的Db对象改作 MasterDb 和 SlaveDb 对象并且把上面的读写方法坐下调整修改后如下
支持读写分离的 DbBase类
public class DbBase{ //是否读写分离(可以配置在配置文件中)private static readonly bool IsReadWriteSeparation true; #region EF上下文对象(主库) protected DbContext MasterDb _masterDb.Value; private readonly LazyDbContext _masterDb new LazyDbContext(() new DbContextFactory().GetWriteDbContext()); #endregion EF上下文对象(主库) #region EF上下文对象(从库) protected DbContext SlaveDb IsReadWriteSeparation ? _slaveDb.Value : _masterDb.Value; private readonly LazyDbContext _slaveDb new LazyDbContext(() new DbContextFactory().GetReadDbContext()); #endregion EF上下文对象(从库) #region 自定义其他方法 /// summary/// 执行存储过程或自定义sql语句--返回集合(自定义返回类型) /// /summary/// param namesql/param/// param nameparms/param/// param namecmdType/param/// returns/returnspublic ListTModel QueryTModel(string sql, ListSqlParameter parms, CommandType cmdType CommandType.Text){ //存储过程exec getActionUrlId name,IDif (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.SqlQueryTModel(sql, parms.ToArray()); var enityList list.ToList(); return enityList;} /// summary/// 自定义语句和存储过程的增删改--返回影响的行数 /// /summary/// param namesql/param/// param nameparms/param/// param namecmdType/param/// returns/returnspublic int Execute(string sql, ListSqlParameter parms, CommandType cmdType CommandType.Text){ //存储过程exec getActionUrlId name,IDif (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数据库 数据层 父类 /// /summary/// typeparam nameT/typeparampublic class DbBaseT : DbBase where T : class, new(){ #region INSERT /// summary/// 新增 实体 /// /summary/// param namemodel/param/// returns/returnspublic void Insert(T model){MasterDb.SetT().Add(model);} /// summary/// 普通批量插入 /// /summary/// param namedatas/parampublic void InsertRange(ListT datas){MasterDb.SetT().AddRange(datas);} #endregion INSERT #region DELETE /// summary/// 根据模型删除 /// /summary/// param namemodel包含要删除id的对象/param/// returns/returnspublic void Delete(T model){MasterDb.SetT().Attach(model);MasterDb.SetT().Remove(model);} /// summary/// 删除 /// /summary/// param namewhereLambda/parampublic void Delete(ExpressionFuncT, bool whereLambda){MasterDb.SetT().Where(whereLambda).Delete();} #endregion DELETE #region UPDATE /// summary/// 单个对象指定列修改 /// /summary/// param namemodel要修改的实体对象/param/// param nameproNames要修改的 属性 名称/param/// param nameisProUpdate/param/// returns/returnspublic void Update(T model, Liststring proNames, bool isProUpdate true){ //将 对象 添加到 EF中MasterDb.SetT().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);ListPropertyInfo 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 namemodel/param/// returns/returnspublic void Update(T model){DbEntityEntry entry MasterDb.EntryT(model);MasterDb.SetT().Attach(model);entry.State EntityState.Modified;} /// summary/// 批量修改 /// /summary/// param namewhereLambda/param/// param nameupdateExpression/parampublic void Update(ExpressionFuncT, bool whereLambda, ExpressionFuncT, T updateExpression){MasterDb.SetT().Where(whereLambda).Update(updateExpression);} /// summary/// 批量修改 /// /summary/// param namemodels/param/// returns/returnspublic void UpdateAll(ListT models){ foreach (var model in models){DbEntityEntry entry MasterDb.Entry(model);entry.State EntityState.Modified;}} /// summary/// 批量统一修改 /// /summary/// param namemodel要修改的实体对象/param/// param namewhereLambda查询条件/param/// param namemodifiedProNames/param/// returns/returnspublic void Update(T model, ExpressionFuncT, bool whereLambda, params string[] modifiedProNames){ //查询要修改的数据ListT listModifing MasterDb.SetT().Where(whereLambda).ToList();Type t typeof(T);ListPropertyInfo proInfos t.GetProperties(BindingFlags.Instance | BindingFlags.Public).ToList();Dictionarystring, PropertyInfo dictPros new Dictionarystring, 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); //批量设置 要修改 对象的 属性foreach (T oModel in listModifing){ //为 要修改的对象 的 要修改的属性 设置新的值proInfo.SetValue(oModel, newValue, null);}}} #endregion UPDATE #region SELECT /// summary/// 根据主键查询 /// /summary/// param nameid/param/// returns/returnspublic T FindById(dynamic id){ return SlaveDb.SetT().Find(id);} /// summary/// 获取默认一条数据没有则为NULL /// /summary/// param namewhereLambda/param/// returns/returnspublic T FirstOrDefault(ExpressionFuncT, bool whereLambda null){ if (whereLambda null){ return SlaveDb.SetT().FirstOrDefault();} return SlaveDb.SetT().FirstOrDefault(whereLambda);} /// summary/// 获取全部数据 /// /summary/// returns/returnspublic ListT GetAll(string ordering null){ return ordering null? SlaveDb.SetT().ToList(): SlaveDb.SetT().OrderBy(ordering).ToList();} /// summary/// 带条件查询获取数据 /// /summary/// param namewhereLambda/param/// param nameordering/param/// returns/returnspublic ListT GetAll(ExpressionFuncT, bool whereLambda, string ordering null){ var iQueryable SlaveDb.SetT().Where(whereLambda); return ordering null? iQueryable.ToList(): iQueryable.OrderBy(ordering).ToList();} /// summary/// 带条件查询获取数据 /// /summary/// param namewhereLambda/param/// returns/returnspublic IQueryableT GetAllIQueryable(ExpressionFuncT, bool whereLambda null){ return whereLambda null ? SlaveDb.SetT() : SlaveDb.SetT().Where(whereLambda);} /// summary/// 获取数量 /// /summary/// param namewhereLambd/param/// returns/returnspublic int GetCount(ExpressionFuncT, bool whereLambd null){ return whereLambd null ? SlaveDb.SetT().Count() : SlaveDb.SetT().Where(whereLambd).Count();} /// summary/// 判断对象是否存在 /// /summary/// param namewhereLambd/param/// returns/returnspublic bool Any(ExpressionFuncT, bool whereLambd){ return SlaveDb.SetT().Where(whereLambd).Any();} /// summary/// 分页查询 /// /summary/// param namepageIndex当前页码/param/// param namepageSize每页大小/param/// param namerows总条数/param/// param nameorderBy排序条件一定要有/param/// param namewhereLambda查询添加可有可无/param/// param nameisOrder是否是Order排序/param/// returns/returnspublic ListT PageTKey(int pageIndex, int pageSize, out int rows, ExpressionFuncT, TKey orderBy, ExpressionFuncT, bool whereLambda null, bool isOrder true){IQueryableT data isOrder ?SlaveDb.SetT().OrderBy(orderBy) :SlaveDb.SetT().OrderByDescending(orderBy); if (whereLambda ! null){data data.Where(whereLambda);}rows data.Count(); return data.PageBy((pageIndex - 1) * pageSize, pageSize).ToList();} /// summary/// 分页查询 /// /summary/// param namepageIndex当前页码/param/// param namepageSize每页大小/param/// param namerows总条数/param/// param nameordering排序条件一定要有/param/// param namewhereLambda查询添加可有可无/param/// returns/returnspublic ListT Page(int pageIndex, int pageSize, out int rows, string ordering, ExpressionFuncT, bool whereLambda null){ // 分页 一定注意 Skip 之前一定要 OrderByvar data SlaveDb.SetT().OrderBy(ordering); if (whereLambda ! null){data data.Where(whereLambda);}rows data.Count(); return data.PageBy((pageIndex - 1) * pageSize, pageSize).ToList();} /// summary/// 查询转换 /// /summary/// typeparam nameTDto/typeparam/// param namewhereLambda/param/// returns/returnspublic ListTDto SelectTDto(ExpressionFuncT, bool whereLambda){ return SlaveDb.SetT().Where(whereLambda).SelectTDto().ToList();} #endregion SELECT #region ORTHER /// summary/// 执行存储过程或自定义sql语句--返回集合 /// /summary/// param namesql/param/// param nameparms/param/// param namecmdType/param/// returns/returnspublic ListT Query(string sql, ListSqlParameter parms, CommandType cmdType CommandType.Text){ return QueryT(sql, parms, cmdType);} /// summary/// 提交保存 /// /summary/// returns/returnspublic int SaveChanges(){ return MasterDb.SaveChanges();} /// summary/// 回滚 /// /summarypublic void RollBackChanges(){ var items MasterDb.ChangeTracker.Entries().ToList();items.ForEach(o o.State EntityState.Unchanged);} #endregion ORTHER}
DbBase 这样简单的读写分离就实现了实现逻辑也比较清晰方便扩展。 进一步改造实现多从库读取 一般做读写分离都会做一主多从特别对读取量比较大的项目这样多库读取就能减轻读库的压力。所以对于上面的方法做下改造。 上面可以看到主库和从库都是通过 DbContextFactory 这个类来获取的在GetReadDbContext 方法中每次都是获取 ReadDbContext 这个对象。那么对于多个从库的情况下每次读取到底要去哪个库读取数据呢这里就是一个算法规则的问题了或者说是策略吧如果使用过nginx的朋友就知道nginx本身内部在实现负载均衡的时候提供了多种策略比如轮询,加权轮询,ip_hash等策略。其实上面获取同一个ReadDbContext 的方法也算一种策略叫单一策略每次都获取单一的对象。 多从库的情况下我们简单的来实现另一种获取策略随机策略每次都随机获取到一个从库的对象这种是最简单的策略当然正式使用的话大家可以发挥自己的创造力写出多了的算法策略。
首先定义一个策略接口方便策略的扩展和切换代码如下
IReadDbStrategy 接口 /// summary/// 从数据库获取策略接口 /// /summarypublic interface IReadDbStrategy{ /// summary/// 获取读库 /// /summary/// returns/returnsDbContext GetDbContext();}
单从库情况下定义一个单一策略代码如下
单一策略 /// summary/// 单一策略 /// /summarypublic class SingleStrategy : IReadDbStrategy{ public DbContext GetDbContext(){ return new ReadDbContext();}}
多从库情况下定义一个随机策略代码如下
随机策略 /// summary/// 随机策略 /// /summarypublic class RandomStrategy : IReadDbStrategy{ //所有读库类型public static ListType DbTypes; static RandomStrategy(){LoadDbs();} //加载所有的读库类型static void LoadDbs(){DbTypes new ListType(); 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:这里可以自己通过注入的方式来实现就会更加灵活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;}} 这样简单的一主多从也实现了。 参考文章 http://www.cnblogs.com/zhaopei/p/5721789.html http://www.cnblogs.com/GuZhenYin/p/5482288.html
回到顶部
源码分享 所有的代码提供给大家的更多的是一种思路和学习的参考如果有什么不足的地方也欢迎大家批评指正如果觉得对你有帮助不要吝啬你的鼠标帮忙点个星点个赞吧。 源码地址 https://github.com/qq1206676756/EF_DbHelper
原文地址http://www.cnblogs.com/qtqq/p/6942312.html.NET社区新闻深度好文微信中搜索dotNET跨平台或扫描二维码关注