之前我老大去网上找了一个DAL里面操作数据库的通用类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 |
|
可以看到里面包含了对SH_SetBase的特殊处理,这个有必要解释一下,因为老大之前的设计是数据库表分为两类,一种是记录表(日志表),他们继承自SH_LogBase,一种是姑且说是对象表吧,他们继承自SH_SetBase,然后全部表继承自SH_Base表,为什么要这样设计呢,其中一个原因是对象表里的数据不能真删除,他们一般是有外键的,级联删除的话删除太多了,而且里面的数据也很重要,只能假删除,所以SH_SetBase得有是否删除IsDelete字段,假删除的话那部分数据是不会再取出来的,除了假删除应该还可以禁用,比如冻结账号,冻结后又可以解冻,这就需要是否启用字段IsEnable字段。
SH_Base表:
public class SH_Base { /// <summary> /// 标识,自动增加 /// </summary> [Key] public long ID { get; set; } /// <summary> /// 备注 /// </summary> [Display(Name = "备注")] public string Summary { get; set; } }
SH_LogBase表:
public class SH_LogBase : SH_Base { private DateTime _logTime = DateTime.Now; /// <summary> /// 记录时间,默认当前时间 /// </summary> [Display(Name = "记录时间")] public DateTime LogTime { get { return _logTime; } set { _logTime = value; } } }
SH_SetBase表:
public class SH_SetBase : SH_Base { /// <summary> /// 是否删除,默认未删除 /// </summary> [Display(Name = "是否删除")] public bool IsDelete { get; set; } private bool _isEnable = true; /// <summary> /// 是否启用,默认为启用 /// </summary> [Display(Name = "是否启用")] public bool IsEnable { get { return _isEnable; } set { _isEnable = value; } } private DateTime _lastUpdateTime = DateTime.Now; /// <summary> /// 最后更新时间,默认当前时间 /// </summary> [Display(Name = "最后更新时间")] public DateTime LastUpdateTime { get { return _lastUpdateTime; } set { _lastUpdateTime = value; } } }
可我发觉DalHelper太臃肿了,而且里面用到了反射,我一直避免用反射,觉得那货效率比较低。后来看了C#线程内唯一的单例用法。把DalHelper拆分成两个BaseDal和SetBaseDal类,他们继承自IBaseDal类,里面的数据库采用线程内单例模式:
public class DbContextFactory { public static ShopContext GetCurrentDbContext() { var db = (ShopContext)CallContext.GetData("ShopContext"); if (db != null) return db; db = new ShopContext(); CallContext.SetData("ShopContext", db); return db; } }
或许有些同志会好奇线程内单例模式有什么好处或者什么用,我这里简单做个测试:
public static ShopContext GetCurrentDbContext() { //单例模式:保证线程实例唯一 var db = (ShopContext)CallContext.GetData("ShopContext"); if (db != null) { File.AppendAllText("E:\\ShopDb.txt", Thread.CurrentThread.ManagedThreadId + "旧"); return db; } File.AppendAllText("E:\\ShopDb.txt", Environment.NewLine + Thread.CurrentThread.ManagedThreadId + "新"); db = new ShopContext(); CallContext.SetData("ShopContext", db); return db; }
然后运行网站程序,随便乱点打开ShopDb.txt一看
可以看出我每点一次网页链接,控制器的Action接收到请求后就会新建一个线程来处理这次请求,由于我在该线程内一直没有另开线程,所以看到的生成的文件内每次请求就会产生一个新的ShopContext,在单次网页请求内用的都是同一个ShopContext的,这样你可能会说有问题呀,单次网页请求共用一个ShopContext,要是某次数据库请求太耗时,难道后续的操作要一直等待吗,这个问题确实存在,解决的办法就是数据库请求采用异步请求,单个线程共用ShopContext有个好处就是单次请求内只需新建一次ShopContext,不用老在内存中新建这个对象,这样的话可能你会说像线程池一样,搞个ShopContext池不是更好吗,线程要的时候去池中取一个ShopContext出来,不用的时候归还给池,这样ShopContext在内存中新建销毁的次数就可以达到最少次数了,这个我就不研究下去了。
接口IBaseDal类:
interface IBaseDal<T> { T Find(long id); T First(Expression<Func<T, bool>> predicate); T Add(T entity); bool Update(T entity); bool Delete(long id); bool Delete(T entity); bool Delete(IEnumerable<T> entities); bool Exist(Expression<Func<T, bool>> predicate); int Count(Expression<Func<T, bool>> predicate); int Sum(Expression<Func<T, bool>> predicate, Expression<Func<T, int>> selector); decimal Sum(Expression<Func<T, bool>> predicate, Expression<Func<T, decimal>> selector); IQueryable<T> LoadEntities(); IQueryable<T> LoadEntities(Expression<Func<T, bool>> predicate); IQueryable<T> LoadPageEntities<TS>(PageContent page, Expression<Func<T, bool>> predicate, Expression<Func<T, TS>> keySelector, bool isAsc); }
日志类使用的BaseDal类:
public class BaseDal<T> : IBaseDal<T> where T : class , new() { public static ShopContext Db { get { return DbContextFactory.GetCurrentDbContext(); } } public virtual T Find(long id) { return Db.Set<T>().Find(id); } public virtual T First(Expression<Func<T, bool>> predicate) { return Db.Set<T>().FirstOrDefault(predicate); } public virtual T Add(T entity) { Db.Set<T>().Add(entity); return entity; } public virtual bool Update(T entity) { Db.Entry(entity).State = EntityState.Modified; return true; } public virtual bool Delete(long id) { return Delete(Find(id)); } public virtual bool Delete(T entity) { Db.Set<T>().Remove(entity); return true; } public virtual bool Delete(IEnumerable<T> entities) { Db.Set<T>().RemoveRange(entities); return true; } public virtual bool Exist(Expression<Func<T, bool>> predicate) { return Db.Set<T>().Any(predicate); } public virtual int Count(Expression<Func<T, bool>> predicate) { return Db.Set<T>().Count(predicate); } public virtual int Sum(Expression<Func<T, bool>> predicate, Expression<Func<T, int>> selector) { try { return Db.Set<T>().Where(predicate).Sum(selector); } catch { return 0; } } public virtual decimal Sum(Expression<Func<T, bool>> predicate, Expression<Func<T, decimal>> selector) { try { return Db.Set<T>().Where(predicate).Sum(selector); } catch { return 0; } } public virtual IQueryable<T> LoadEntities() { return Db.Set<T>().AsQueryable(); } public virtual IQueryable<T> LoadEntities(Expression<Func<T, bool>> predicate) { return Db.Set<T>().Where(predicate); } public virtual IQueryable<T> LoadPageEntities<TS>(PageContent page, Expression<Func<T, bool>> predicate, Expression<Func<T, TS>> keySelector, bool isAsc) { page.TotalItems = Count(predicate); var lst = Db.Set<T>().Where(predicate); lst = isAsc ? lst.OrderBy(keySelector) : lst.OrderByDescending(keySelector); return lst.Skip(page.PageSize * (page.PageIndex - 1)) .Take(page.PageSize); } }
对象类使用的SetBaseDal类:
public class SetBaseDal<T> : IBaseDal<T> where T : SH_SetBase, new() { public static ShopContext Db { get { return DbContextFactory.GetCurrentDbContext(); } } public virtual T Find(long id) { var t = Db.Set<T>().Find(id); return t.IsDelete ? null : t; } public virtual T First(Expression<Func<T, bool>> predicate) { return Db.Set<T>().Where(o => !o.IsDelete).FirstOrDefault(predicate); } public virtual T Add(T entity) { Db.Set<T>().Add(entity); return entity; } public virtual bool Update(T entity) { entity.LastUpdateTime = DateTime.Now; Db.Entry(entity).State = EntityState.Modified; return true; } public virtual bool Delete(long id) { return Delete(Find(id)); } public virtual bool Delete(T entity) { entity.IsDelete = true; entity.LastUpdateTime = DateTime.Now; Db.Entry(entity).State = EntityState.Modified; return true; } public virtual bool Delete(IEnumerable<T> entities) { foreach (var entity in entities) { Delete(entity); } return true; } public virtual bool Exist(Expression<Func<T, bool>> predicate) { return Db.Set<T>().Where(o => !o.IsDelete).Any(predicate); } public virtual int Count(Expression<Func<T, bool>> predicate) { return Db.Set<T>().Where(o => !o.IsDelete).Count(predicate); } public virtual int Sum(Expression<Func<T, bool>> predicate, Expression<Func<T, int>> selector) { try { return Db.Set<T>().Where(predicate).Where(o => !o.IsDelete).Sum(selector); } catch { return 0; } } public virtual decimal Sum(Expression<Func<T, bool>> predicate, Expression<Func<T, decimal>> selector) { try { return Db.Set<T>().Where(predicate).Where(o => !o.IsDelete).Sum(selector); } catch { return 0; } } public virtual IQueryable<T> LoadEntities() { return Db.Set<T>().Where(o => !o.IsDelete); } public virtual IQueryable<T> LoadEntities(Expression<Func<T, bool>> predicate) { return Db.Set<T>().Where(predicate).Where(o => !o.IsDelete); } public virtual IQueryable<T> LoadPageEntities<TS>(PageContent page, Expression<Func<T, bool>> predicate, Expression<Func<T, TS>> keySelector, bool isAsc) { page.TotalItems = Count(predicate); var lst = Db.Set<T>() .Where(predicate) .Where(o => !o.IsDelete); lst = isAsc ? lst.OrderBy(keySelector) : lst.OrderByDescending(keySelector); return lst.Skip(page.PageSize * (page.PageIndex - 1)) .Take(page.PageSize); } }
里面还有个PageContent类:
public class PageContent { public int PageIndex { get; set; } public int PageSize { get; set; } public int TotalItems { get; set; } public int TotalPages { get { if (PageSize == 0) return 0; return (TotalItems + PageSize - 1)/PageSize; } } }
可以看出BaseDal和SetBaseDal之间最大的区别就是BaseDal删除是真删除,SetBaseDal不能真删除,调用删除方法自动执行假删除,而且SetBaseDal获取数据等所有方法都把假删除的数据排除掉了。可能你会问既然假删除的数据都被排除掉了,那他们还有存在的必要吗,当然有必要了,因为除了用户看这些数据,管理人员是可以直接去数据库看的,那就能看到那些被假删除的数据了。
写好这BaseDal和SetBaseDal类之后,BLL的类就可以继承自这两个类或者不继承,在类里使用者两个类,设计理念我不在行,貌似不建议直接继承Dal的通用类,而是在BLL定义一个接口,然后实现每个表的该接口,并在该类里引用Dal里的通用类,不过我觉得怎么方便就怎么用吧,何必一定要符合设计理念呢。还有贫血模型也是,按照面向对象理念是不能设计为贫血模型的,因为一个对象必须是完整的,里面的属性就像一个对象的血肉肢体,而里面的方法就像一个对象能够进行的活动,缺少了方法的实体类就像是尸体类,光有肢体不能动,可我还是那句话,怎么方便就怎么用吧,为何一定要面向对象呢,而且我认为光有属性也有好处,当我只需要该对象来传值的时候其实是不需要该对象的行为的,这样的话贫血模型加载到内存中占用的内存就少了些了。当然,这只是我的胡言乱语,大神请直接忽视。。。