【我们一起写框架】领域驱动设计的CodeFirst框架(一)—序篇

前言 领域驱动设计,其实已经是一个很古老的概念了,但它的复杂度依旧让学习的人头疼不已。 互联网关于领域驱动的文章有很多,每一篇写的都很好,理解领域驱动设计的人都看的懂。 不过,这些文章对于那些初学者而言,还是如同天书一样。 买本驱动领域的书来看?别逗了,这可不是C#语法入门,哪里有书能写明白的。 想学会领域驱动设计,只有一途——实践,不断的实践。 领域驱动设计是什么? 领域驱动设计就是我们俗称的DDD,英文全拼是Domain-Driven Design。 我认为,理解领域驱动设计的第一步是,顾名思义;所以,让我们先直白的通过名字来解释看看。 领域驱动设计:用业务领域来做模块分割,以领域为核心思想设计框架,用设计好的领域来驱动系统实现。 如何?这样是不是就好理解了。 其实,领域驱动设计,和我们之前常用的模型驱动设计很相似。其核心区别,也就是一个聚合的概念。 虽然,现在看来,CodeFirst中的聚合太普遍了,但早在十几年前,聚合可是一个让我们头疼的难题,因为那个时代还没有CodeFirst这么便捷的框架。 什么?你不知道聚合是什么? 别担心,我们在后续实现框架的地方,结合代码把这些聚合啦,值对象啦,等等名词一一讲解。 其实,以现在的技术框架的成熟度,聚合这种东西,不理解也就不理解了,无所谓的。 领域驱动设计的意义 虽然,我不想把领域驱动设计搞的那么神秘,但,事实上,领域驱动设计确实挺难学的。 虽然,我们有了CodeFirst这样优秀的框架,但那只是针对使用者,而对设计者而言,CodeFirst并没有减少设计逻辑。所以,想学会领域驱动设计,还是要有一点耐心,并花一点时间,付诸于实践。 虽然,领域驱动设计很复杂,但,我认为它是值得我们付出时间和心血学习的。 因为,驱动领域设计是技术思维的一个分水岭,学会了这种技术思维后,会对框架设计的理解更上一个台阶。 那么,让我们一起做一个领域驱动的框架,在实践中领会这门技艺吧。 领域驱动设计的实现 我们即将编写的框架是基于Entity Framework的,所以越熟悉Entity Framework越好,如果你不熟悉EF,那也没关系,因为我们是从头一步一步编写的。 下面让我们一起编写框架吧。 首先,我们创建项目如下: 接下来我们把相关的DLL放到KibaDDD程序集下待用。 然后我们编写核心代码程序集Repository。 首先为Repository程序集引入外部DLL[EntityFramework,EntityFramework.Extended,EntityFramework.SqlServer,CodeFirstStoredProcs],同时,再为程序集引入Utility程序集。 然后我们开始设计Repository程序集的布局。 如上图所示,我们建立了Repository程序集的布局,布局中的文件夹及文件作用如下: TableMapping文件夹:用于存储数据表的映射关系。 TableModel文件夹:用于存储数据表模型。 TableRepository文件夹:用于操作数据表。 DateBaseContext文件:管理数据库的核心文件。 RepositoryStatic文件:存储静态的DateBaseContext对象,供其他程序集调用,实现线程内,使用同一个DateBaseContext对象,减少内存开销。 Repository的实现 TableModel TableModel中我们建立了一个表——Kiba_User,代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public partial class Kiba_User { [Key] public int UserId { get; set; } [Required] [StringLength(50)] public string UserName { get; set; } [StringLength(200)] public string UserNickName { get; set; [StringLength(100)] public string Password { get; set; } public int? Age { get; set; } public int? Sex { get; set; } [StringLength(500)] public string Remark { get; set; } } 代码很简单,就是把数据表和其字段转换成了类和属性,我们可以把这个类暂时理解为表的数据模型。 TableMapping TableMapping中我们建立Kiba_User的数据模型表与数据库表的映射关系,代码如下所示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class Kiba_UserMap : EntityTypeConfiguration { public Kiba_UserMap() { this.Property(e => e.UserName) .IsUnicode(false); this.Property(e => e.UserNickName) .IsUnicode(false); this.Property(e => e.Password) .IsUnicode(false); this.Property(e => e.Remark) .IsUnicode(false); } } 从代码中我们可以发现,映射只对部分字符串类型的属性进行了映射,而其他属性,并没有做映射处理。 原因是这样的,没有显示映射处理的属性,会默认映射到同名的数据表字段上;所以这里节省了一些代码量。 DateBaseContext文件 表的数据模型和映射我们已经编写完了,并且,我们还编写了仓储用来对表进行操作;但,这样还不能让数据库和代码模型关联到一起。 我们还需要编写DateBaseContext文件,通过DateBaseContext文件编写,我们就可以把表模型和表映射与数据库关联了。 DateBaseContext文件的代码如下所示: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public partial class DateBaseContext : DbContext { public DateBaseContext() : base("name=DateBaseContext") { this.Configuration.ValidateOnSaveEnabled = true;//保存时验证 this.Configuration.AutoDetectChangesEnabled = true;//跟踪变化 this.Configuration.LazyLoadingEnabled = true;//懒惰加载 this.Configuration.ProxyCreationEnabled = true;//代理创建数据库 } #region Table List public virtual DbSet Kiba_User { get; set; } #endregion protected override void OnModelCreating(DbModelBuilder modelBuilde { modelBuilder.Configurations.Add(new Kiba_UserMap()); } } 代码很简单,下面我们一起来解读下DateBaseContext文件里的代码。 首先是DateBaseContext继承了DbContext类;DbContext可以理解为微软提供的,专门来管理数据库和代码之间的关系的类。 然后再构造函数DateBaseContext()里,可以看到,我们在构造函数中做了几项基础配置,代码中已经做了相应的注释。 其中this.Configuration.ProxyCreationEnabled属性,我们重点讲一下。 当ProxyCreationEnabled属性设置为True时,我们一旦运行系统,系统会自动的,数据模型同步到数据库,并且会创建一个__MigrationHistory表,来记录同步的内容。 PS:【虽然,在领域驱动设计的理念中,是先有表的数据模型,然后在建立表结构。但,这只是理念,我们运用的时候,先建立表在建立数据模型也是可以的。我这里只是为了简单的实现,所以将ProxyCreationEnabled设置为了True】 接下来,我们定义了一个public virtual DbSet Kiba_User { get; set; }属性。 Kiba_User 这个属性,我们可以把他理解为,数据库表在代码世界的代理,如果我们想对数据库表内容进行查询和修改,只要对这个代理进行修改,就会自动同步到数据库了。 然后我们重写了OnModelCreating方法,在OnModelCreating里,把我们刚刚建立的映射关系添加了进去,这样数据库的表,就被我们立体的加载到了代码世界。 TableRepository TableRepository中主要是应用DateBaseContext来对表进行增删改查的处理,理论上TableRepository是修改数据库的唯一入口; 我们首先,先看下BaseRepository类;代码如下: 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 public class BaseRepository { public DateBaseContext Database { get { var context = RepositoryStatic.DateBaseContext as DateBaseContext; if (context == null) { context = new DateBaseContext(); RepositoryStatic.DateBaseContext = context; } return context; } } public int SaveChanges() { int i = 0; int saveCount = 0; bool saveFailed; do { saveFailed = false; try { saveCount++; i = Database.SaveChanges(); Logger.Debug("SaveChanges Retrun:" + i); } catch (DbUpdateConcurrencyException ex) { if (saveCount > 3) { throw new Exception("服务器繁忙,请稍后"); } Logger.Error("DbUpdateConcurrencyException保存次数:" + saveCount, ex); saveFailed = true; try { ex.Entries.Single().Reload(); } catch (Exception exReload) { Logger.Info("exReload保存失败"); throw exReload; } } catch (DbUpdateException ex) { if (ex.Message.Contains("与另一个进程被死锁在 锁 资源上,并且已被选作死锁牺牲品。请重新运行该事务。")) { throw new Exception("服务器繁忙,请稍后"); } else { throw ex; } } catch (DbEntityValidationException dbEx) { Logger.Error(dbEx); throw dbEx; } catch (Exception ex) { Logger.Info("SaveChanges保存失败"); throw ex; } } while (saveFailed); return i; } } 这里我们主要定义一个属性Database和一个方法SaveChanges。 Database就是DateBaseContext类的实例,相当于代码世界的数据库。 SaveChanges就是调用Database的SaveChanges方法来保存数据的修改,当然,我们对该方法进行了一些封装,让他更饱满一些。 然后我们在一起看下表的独立仓储Kiba_UserRepo,代码如下: 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 public class Kiba_UserRepo : BaseRepository { public List GetSelector(Expression> selector, Expression> where) { return Database.Kiba_User.Where(where).Select(selector).ToList(); } public List GetWhere(Expression> where, int currentPage, int pageCount) { return Database.Kiba_User.Where(where).OrderByDescending(p => p.UserId).Skip((currentPage - 1) * pageCount).Take(pageCount).ToList(); } public int GetWhereCount(Expression> where) { return Database.Kiba_User.Where(where).Count(); } public Kiba_User Add(Kiba_User model) { var addModel = Database.Kiba_User.Add(model); return addModel; } public Kiba_User Delete(Kiba_User model) { var delModel = Database.Kiba_User.Remove(model); return delModel; } } 表仓储里的代码很简单,就是普通的LINQ增删改查。 ---------------------------------------------------------------------------------------------------- 到此,框架的基本雏形就已经编写完成了,接下来我们做一下简单调用,测试一下。 在KibaDDD项目建立测试类——TestRun;代码如下: 1 2 3 4 5 6 7 8 9 public class TestRun { public TestRun() { Kiba_UserRepo repo = new Kiba_UserRepo(); repo.Add(new Kiba_User() { UserName = "kiba518" }); repo.SaveChanges(); } } 运行结果: 数据库无中生有的,为我们创建了表Kiba_User,并且数据也顺利的插入进了数据库表。 这样,我们的领域驱动框架就已经完成了雏形搭建,下一篇文章将进一步搭建,实现领域驱动独有的聚合。 ---------------------------------------------------------------------------------------------------- 框架代码已经传到Github上了,欢迎大家下载。 Github地址:https://github.com/kiba518/KibaDDD ---------------------------------------------------------------------------------------------------- 注:此文章为原创,欢迎转载,请在文章页面明显位置给出此文链接! 若您觉得这篇文章还不错,请点击下右下角的【推荐】,非常感谢! https://www.cnblogs.com/kiba/p/9953739.html
50000+
5万行代码练就真实本领
17年
创办于2008年老牌培训机构
1000+
合作企业
98%
就业率

联系我们

电话咨询

0532-85025005

扫码添加微信