Apworks框架实战(六):使用基于Entity Framework的仓储基础结构
在前面的章节中,我们已经设计了一个简单的领域模型,接下来我们希望能够实现领域模型的持久化及查询。在Apworks中,实现了面向Entity Framework、NHibernate以及MongoDB的仓储基础结构。在本章节中,我将向大家介绍如何在Apworks中使用基于Entity Framework的仓储机制。
搭建基于Entity Framework的基础结构
在使用Apworks提供的仓储服务之前,我们首先需要搭建好基于Entity Framework的基础结构,以便接下来的Apworks能够使用这些基础结构功能,并利用Entity Framework实现领域模型对象生命周期的管理。
从DbContext开始
我们采用Entity Framework Code First的编程模型,因此,我们将从DbContext开始入手,为Entity Framework仓储机制的使用做好准备工作。
首先,在【EasyMemo.Repositories】项目上单击鼠标右键,选择【管理NuGet程序包】选项。在弹出的【管理NuGet程序包】的【搜索联机】文本框中,输入关键字【apworks】。在过滤的列表中,找到【Apworks.Repositories.EntityFramework】,然后单击【安装】按钮。
说明:安装该程序包也会顺带将其所依赖的程序包一并安装到【EasyMemo.Repositories】项目中,这些程序包包括:
- Apworks 2.5.5662.37915
- Castle.Core 3.3.1
- EntityFramework 6.1.1
接下来,在【EasyMemo.Repositories】项目中,新建一个名为EasyMemoContext的类,该类从System.Data.Entity.DbContext类继承,代码如下:
public class EasyMemoContext : DbContext { public EasyMemoContext() : base("EasyMemoDB") { } public DbSet<Account> Accounts { get; set; } public DbSet<Role> Roles { get; set; } public DbSet<Memo> Memos { get; set; } }
这就是标准的Entity Framework Code First的用法,不过,Apworks的最佳实践中建议,此处仅对聚合根定义DbSet属性,这样能使DbContext的定义变得非常简洁直观。
下一步就是针对领域模型中的实体定义一些类型/数据库映射。根据标准的Entity Framework使用方法,我们可以定义一系列继承于EntityTypeConfiguration泛型类的子类,在这些子类中定义映射规则,并在EasyMemoContext的OnModelCreating重载方法中将这些子类的实例添加到Configurations集合里;或者也可以直接在OnModelCreating方法中定义映射规则。我还是比较偏向于前面这种方式,即针对每个需要配置映射的实体,都创建一个继承于EntityTypeConfiguration的子类,虽然看起来会有很多额外的类定义,但这样做会使得代码结构有着更好的可读性。例如,针对Account对象,我们可以定义映射配置类型如下:
public class AccountEntityConfiguration : EntityTypeConfiguration<Account> { public AccountEntityConfiguration() { ToTable("Accounts"); HasKey(x => x.ID); Property(x => x.ID) .IsRequired() .HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); Property(x => x.DateCreated).IsRequired(); Property(x => x.DateLastLogon).IsOptional(); Property(x => x.DisplayName) .IsRequired() .IsUnicode() .HasMaxLength(32); Property(x => x.Email) .IsRequired() .IsUnicode() .HasMaxLength(64); Property(x => x.IsDeleted).IsOptional(); Property(x => x.Name).IsRequired() .IsUnicode() .HasMaxLength(16); Property(x => x.Password).IsRequired() .IsUnicode() .HasMaxLength(4096); } }
然后将该类的实例添加到OnModelCreating重载方法中:
protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new AccountEntityConfiguration()); }
OK,接下来使用类似的方法针对领域模型中必要的实体类型定义映射配置类,并依次将这些类的实例添加到OnModelCreating重载方法中。限于篇幅,在此就不一一列出代码了,您可以在本章节结尾部分点击下载代码的链接,把源代码下载到本地作参考。
设置数据库初始化策略
Entity Framework本身支持以下几种数据库初始化策略:
- MigrateDatabaseToLatestVersion:使用Code First数据库迁移策略,将数据库更新到最新版本
- NullDatabaseInitializer:一个什么都不干的数据库初始化器
- CreateDatabaseIfNotExists:顾名思义,如果数据库不存在则新建数据库
- DropCreateDatabaseAlways:无论数据库是否存在,始终重建数据库
- DropCreateDatabaseIfModelChanges:仅当领域模型发生变化时才重建数据库
在实际应用当中,我们可以直接使用以上数据库初始化策略,在调用Database对象的Initialize方法时,Entity Framework就会根据所选择的初始化策略以及上面的映射配置信息来初始化数据库。为了演示目的,我们希望能够在数据库初始化的同时,为我们准备一些数据,以便对今后的内容进行介绍,因此,我们可以自定义一套数据库初始化策略,并在其中将所需的数据写入数据库。
首先,在【EasyMemo.Repositories】项目中,新建一个名为DatabaseInitializeStrategy的类,并使其继承DropCreateDatabaseIfModelChanges类型:
public class DatabaseInitializeStrategy : DropCreateDatabaseIfModelChanges<EasyMemoContext> { }
然后,在该类型中重载Seed方法,添加如下代码:
public class DatabaseInitializeStrategy : DropCreateDatabaseIfModelChanges<EasyMemoContext> { protected override void Seed(EasyMemoContext context) { var adminPermission = new Permission { Privilege = Privilege.SystemAdministration, Value = PermissionValue.Allow }; var administrators = new Role { Name = "系统管理员", Description = "执行系统管理任务的一组账户", Permissions = new List<Permission> {adminPermission} }; var administrator = new Account { DateCreated = DateTime.UtcNow, DisplayName = "管理员", Email = "admin@easymemo.com", Name = "admin", Password = "admin", Roles = new List<Role> {administrators} }; context.Accounts.Add(administrator); base.Seed(context); } }
于是,我们就有了自己的数据库初始化策略,下一步就是在EasyMemo的系统中使用这个策略。
运行我们的代码
打开【EasyMemo.Services】项目,以上述相同的方法,通过【管理NuGet程序包】功能,添加对【Apworks.Repositories.EntityFramework】程序包的引用。然后,在Appp_Start目录下,新建一个名为DatabaseConfig的类:
该类的代码如下:
public static class DatabaseConfig { public static void Initialize() { Database.SetInitializer(new DatabaseInitializeStrategy()); new EasyMemoContext().Database.Initialize(true); } }
接下来,打开【EasyMemo.Services】项目下的Global.asax.cs文件,向Application_Start方法添加对DatabaseConfig.Initialize的调用:
public class WebApiApplication : System.Web.HttpApplication { protected void Application_Start() { GlobalConfiguration.Configure(WebApiConfig.Register); DatabaseConfig.Initialize(); } }
打开【EasyMemo.Services】项目的web.config文件,找到其中的entityFramework节点,对该节点进行配置,使得Entity Framework能够使用您所指定的SQL Server数据库:
<entityFramework> <defaultConnectionFactory type="System.Data.Entity.Infrastructure.SqlConnectionFactory, EntityFramework"> <parameters> <parameter value="Data Source=localhost; Initial Catalog=EasyMemoDB; Integrated Security=True; Connect Timeout=120; MultipleActiveResultSets=True" /> </parameters> </defaultConnectionFactory> <providers> <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" /> </providers> </entityFramework>
现在,请将【EasyMemo.Services】项目设置为启动项目,然后直接按F5,稍等片刻,当浏览器出现如下画面后,我们就可以到SQL Server中找到由Entity Framework自动产生的数据库了:
打开【Microsoft SQL Server Management Studio】,连接到所配置的数据库实例,我们可以看到EasyMemoDB已经出现在数据库列表中:
并且可以查询到我们预先准备好的数据:
由Entity Framework自动产生的数据库结构如下:
当然,目前我们无需对这个数据模型关注太多,毕竟我们不打算面向数据库编程。
开始使用基于Entity Framework的Apworks仓储服务
在使用Apworks仓储服务之前,我们首先需要对Apworks的整个运行环境进行配置。基于之前的分层结构的讨论,EasyMemo.Services项目是一个位于服务端的RESTful API项目,它由ASP.NET Web API 2.0实现。因此,针对Apworks框架运行环境的配置也会在这个项目中发生。为了能够让Web API控制器能够得到Apworks仓储及其上下文的实例,我们采用了IoC技术,并选择Microsoft Unity作为依赖注入框架,这也是Apworks框架目前支持的唯一一种依赖注入框架。当然,Apworks的依赖注入系统是可以扩展的,您可以根据自己项目需要对其进行扩展,使其能够支持多种主流的依赖注入框架。
配置Apworks的运行环境
首先,通过【管理NuGet程序包】,向【EasyMemo.Services】项目中添加以下程序包引用:
- Apworks.ObjectContainers.Unity:Apworks对Unity的支持库
- Unity.WebAPI:Unity对ASP.NET Web API的支持,它可以使得ASP.NET Web API能够使用Unity作为依赖注入框架
接下来,与之前添加DatabaseConfig类一样,在【EasyMemo.Services】项目的【App_Start】文件夹下,新建一个名为ApworksConfig的静态类,内容如下:
using Apworks.Application; using Apworks.Config.Fluent; using Apworks.Repositories; using Apworks.Repositories.EntityFramework; using EasyMemo.Repositories; using Microsoft.Practices.Unity; using Unity.WebApi; public static class ApworksConfig { public static void Initialize() { AppRuntime .Instance .ConfigureApworks() .UsingUnityContainerWithDefaultSettings() .Create((sender, e) => { var unityContainer = e.ObjectContainer.GetWrappedContainer<UnityContainer>(); unityContainer.RegisterInstance(new EasyMemoContext(), new PerResolveLifetimeManager()) .RegisterType<IRepositoryContext, EntityFrameworkRepositoryContext>( new HierarchicalLifetimeManager(), new InjectionConstructor(new ResolvedParameter<EasyMemoContext>())) .RegisterType(typeof (IRepository<>), typeof (EntityFrameworkRepository<>)); GlobalConfiguration.Configuration.DependencyResolver = new UnityDependencyResolver(unityContainer); }) .Start(); } }
注意:这里采用的是Fluent Interface(流畅接口)的配置方式。Apworks同时也支持基于web.config/app.config的配置方式,今后有机会我再介绍这部分内容。
然后,同样地,在Global.asax.cs文件的Application_Start方法中,调用上述代码:
public class WebApiApplication : System.Web.HttpApplication { protected void Application_Start() { GlobalConfiguration.Configure(WebApiConfig.Register); DatabaseConfig.Initialize(); ApworksConfig.Initialize(); } }
OK,Apworks的运行环境配置基本上就算完成了。接下来,让我们新建一个简单的RESTful API,来跑通整个流程。
开始我们的ASP.NET Web API之旅
在【EasyMemo.Services】项目中,找到【Controllers】目录,单击鼠标右键,在右键菜单中选择【添加 –> 控制器】。在弹出的【添加基架】对话框中,选择【Web API 2控制器 - 空】:
在弹出的【添加控制器】对话框的【控制器名称】一栏,填入【AccountsController】:
在单击【添加】按钮后,Visual Studio会打开AccountsController的代码编辑界面。此时,我们可以向AccountsController类添加以下代码:
[RoutePrefix("api/accounts")] public class AccountsController : ApiController { private readonly IRepository<Account> accountRepository; private readonly IRepositoryContext unitOfWork; public AccountsController(IRepositoryContext unitOfWork, IRepository<Account> accountRepository) { this.accountRepository = accountRepository; this.unitOfWork = unitOfWork; } [HttpGet] [Route("name/{name}")] public IHttpActionResult GetByName(string name) { var account = this.accountRepository.Find(Specification<Account>.Eval(acct => acct.Name == name)); if (account != null) { return Ok(new { account.Name, account.DisplayName, account.Email, account.DateCreated, account.DateLastLogon }); } throw new Exception(string.Format("The account '{0}' does not exist.", name)); } protected override void Dispose(bool disposing) { if (disposing) { unitOfWork.Dispose(); } base.Dispose(disposing); } }
代码还算简洁吧?让我们再次启动【EasyMemo.Services】项目:将该项目设置为启动项目,然后直接按F5,在一个与上面相同的【403.14 – Forbidden】页面出来后,在浏览器中输入:
http://localhost:30295/api/accounts/name/admin
此时,你就能看到这个RESTful API返回的结果:它返回了admin这个账户的详细信息:
总结
本文详细介绍了如何在Apworks框架中使用Entity Framework并为一个ASP.NET Web API的RESTful服务提供仓储及其上下文的基础结构。从下一讲开始,我们会重点讨论ASP.NET Web API实现的方方面面,包括异常处理、认证与授权、数据传输对象与视图模型等。
源代码下载
请【单击此处】下载截止到本文为止的EasyMemo解决方案源代码。