转载

EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(四)

前言

  • 这一篇,我们终于到了讲解Entity Framework CodeFirst 的时刻了,首先创建实体对象模型,然后会通过配置Fluent API的方式来对实体对象模型进行完整的数据库映射操作。
  • 此篇幅中会涉及到一些Entity Frame的相关概念,会给出初步的解释。如果需要详细了解,可以查阅相关的帮助文档。

EF 实体对象模型的创建

  • EF的实体对象模型大都采用POCO类的方式创建。POCO的全称为Plain_Old_CLR_Object(简单传统CLR对象),是指那些没有从任何类继承,也没有实现任何接口的简单对象。EF CodeFirst可以利用POCO类创建的实体对象来对数据库进行映射,即我们可以通过编写POCO类中的属性和关联POCO类的关系的方式完成对数据库的映射操作。
  • 我们知道数据库的规范通过范式来进行约束,那我们如何通过POCO类的方式来完成对数据库映射以及约束的操作呢?首先我们得把实体对象间的关系创建清楚,实体对象间的关系有三种:一对一、一对多、多对多。接下来我们通过完成示例来演示如何创建这些示例。
  1. 打开解决方案的Entities工程,我们把POCO类都建立在此工程下。没有关注过此系列文章的朋友可以在 第二篇的末尾下载 到解决方案工程文件。创建用户类S_User,与此类关联的对象有S_Role和S_Log,因为一个用户只属于某一个对象,一个用户包含多条操作日志。因此S_User的代码如下:
  public class S_User {  public S_User(){this.S_Logs = new List<S_Log>();}  public long ID { get; set; }  public long RoleID { get; set; }  public string UserName { get; set; }  public string UserPwd { get; set; }  public string IsUse{ get; set; }  public string Phone{ get; set; }  public string Email{ get; set; }  public string Remark { get; set; }  public virtual S_Role S_Role { get; set; }  public virtual ICollection<S_Log> S_Logs { get; set; } } 

2. 接下来是日志类S_Log,与此关联的对象有S_User,即一条日志只属于某一个用户。因此S_Log的代码如下:

public class S_Log   {    public long ID { get; set; }    public long UserID { get; set; }    public DateTime OperationDate { get; set; }    public string  OperationMenu{ get; set; }    public string  OperationType{ get; set; }    public string Detail{ get; set; }    public virtual S_User S_User { get; set; }   } 

3. 接下来是角色类S_Role,与此关联的对象有S_User和S_Menu,即一个角色可以包含多个用户,一个角色用户可以操作多个菜单页面。

因此S_Role代码如下:

  public class S_Role {  public S_Role(){ this.S_Users = new List<S_User>();}  public long ID { get; set; }  public string RoleName { get; set; }  public string Remark { get; set; }  public virtual ICollection<S_User> S_Users { get; set; }  public virtual ICollection<S_Menu> S_Menus { get; set; } } 

4. 接下来是菜单类S_Menu,与此类关联的对象有S_Role,即一个菜单页面可以被多个角色用户操作。但是S_Menu采用的是树级结构的方式,

一个父级菜单包含多个子菜单,一个子菜单只属于某一个父级菜单。即子菜单的PID是父级菜单的ID,因此S_Menu的PID因该是S_Menu中ID

的外键,由于顶级的父级菜单是没有父级菜单的,所以我们可以设置PID为可空类型,S_Menu的代码如下:

  public class S_Menu {  public long ID { get; set; }  public string MenuName { get; set; }  public string Icon { get; set; }  public string Link { get; set; }  public string IsUse { get; set; }  public int Level { get; set; }  public int SerialNO { get; set; }  public Nullable<long> PID { get; set; }  public string Remark { get; set; }  public virtual ICollection<S_Role> S_Roles { get; set; }  public virtual S_Menu Parent { get; set; }  public virtual ICollection<S_Menu> Children { get; set; } } 

5. 接下来是字典类S_TypeInfo,由于没有与之关联的对象,因此,我们只需简单定义属性就可以呢。S_TypeInfo的代码如下:

  public class S_TypeInfo {  public long ID { get; set; }  public string Type { get; set; }  public string Name { get; set; }  public string Value { get; set; }  public string Remark { get; set; } } 

6. 注意:一对多关系中,我们需要对外键的实体进行构造函数进行重载,比如角色中包含多个用户,public S_Role(){this.S_Users = new

List<S_User>();}。多对多关系就不用进行此操作呢。还有一点就是外键必须显示的设置,比如RoleID。因为后面在Fluent API映射数据时

,配置文件中需要用到。

EF 实体上下文的创建

  • 从Nuget上获取EntityFramework,工程选择Concrete,在“程序包管理器控制台”中输入Install-Package EntityFramework命令就可以安装了。

EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(四)

  • 在Concrete工程中添加EFDbContext类来构建领域实体上下文模型。设置我们的数据库连接名称为EFDbContext,修改Web项目下的web.config设置连接名称为EFDbContext,配置好数据库连接字符串后,我们才可以连接数据库。在数据库映射的创建方法OnModelCreating中,我们把映射的配置文件放到Mapper工程下,把数据库的初始化操作放到Initializer工程下。EFDbContext代码如下:
  public class EFDbContext : DbContext {  public EFDbContext()   : base("EFDbContext") { }  public EFDbContext(string nameOrConnectionString)   : base(nameOrConnectionString) {  }  public DbSet<S_Log> S_Logs { get; set; }  public DbSet<S_Menu> S_Menus { get; set; }  public DbSet<S_Role> S_Roles { get; set; }  public DbSet<S_TypeInfo> S_TypeInfos { get; set; }  public DbSet<S_User> S_Users { get; set; }  protected override void OnModelCreating(DbModelBuilder modelBuilder)  {   modelBuilder.Configurations.Add(new S_LogMap());   modelBuilder.Configurations.Add(new S_MenuMap());   modelBuilder.Configurations.Add(new S_RoleMap());   modelBuilder.Configurations.Add(new S_TypeInfoMap());   modelBuilder.Configurations.Add(new S_UserMap());   modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();  //表中都统一设置禁用一对多级联删除   modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>(); //表中都统一设置禁用多对多级联删除   base.OnModelCreating(modelBuilder);  } } 

Fluent API 配置数据库映射

  • 其实如果不使用数据库配置方式,EntityFramework也可以将实体库映射到数据库文件中,只是可能达不到我们预期的数据库设计的目的,因为EntityFramework会采用默认的数据库映射方式来生成数据库。采用Fluent API可以对数据库进行详细的配置,主要包括:
  1. 主键的设置
  2. 属性的设置
  3. 表和字段的设置
  4. 关系的设置
  • 在采用Fluent API方式配置实体时,实体都继承一个类型为EntityTypeConfiguration的泛型类,只有继承此类构建的实体才可以在数据库中映射出对应的约束条件。
  • 首先我们来建立一下S_User的映射文件S_UserMap,代码如下:
  public class S_UserMap : EntityTypeConfiguration<S_User> {     public S_UserMap()     {  // Primary Key  this.HasKey(t => t.ID);  // Properties  this.Property(t => t.UserName).IsRequired().HasMaxLength(20);  this.Property(t => t.UserPwd).IsRequired().HasMaxLength(25);  this.Property(t => t.IsUse).IsRequired().HasMaxLength(2);  this.Property(t => t.Phone).IsOptional().HasMaxLength(11);  this.Property(t => t.Email).IsOptional().HasMaxLength(25);  this.Property(t => t.Remark).IsOptional().HasMaxLength(20);  // Table & Column Mappings  this.ToTable("S_User");  this.Property(t => t.ID).HasColumnName("ID").HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);  this.Property(t => t.UserName).HasColumnName("UserName");  this.Property(t => t.UserPwd).HasColumnName("UserPwd");  this.Property(t => t.IsUse).HasColumnName("IsUse");  this.Property(t => t.Phone).HasColumnName("Phone");  this.Property(t => t.Email).HasColumnName("Email");  this.Property(t => t.Remark).HasColumnName("Remark");  this.Property(t => t.RoleID).HasColumnName("RoleID");  // Relationships  this.HasRequired(t => t.S_Role).WithMany(t => t.S_Users).HasForeignKey(d => d.RoleID);     } } 
  1. S_UserMap继承了EntityTypeConfiguration<S_User>的泛型类
  2. HasKey用来指定那个属性为主键
  3. 在Properties设置中,IsRequired用来设定属性为必须字段,不可为空。HasMaxLength用来设置字段的长度。IsOptional用来设定属性为可选字段,可以为空。
  4. ToTable可以用来设置表的名称,HasColumnName用来设定字段的名称。如果你想数据库字段名和实体类中的属性名不一样,可以在此进行设置。HasDatabaseGeneratedOption用来表示字段列是否为自增长,本示例中,我们的主键采用的long类型的日期流水码,不需要字段编号。所以设置为none。
  5. 在Relationships中,由于一个用户只属于一个角色,所以RoleID就为S_User对象的外键,配置外键的方式如代码所示。
  • 接下来建立S_Role的映射文件S_RoleMap,代码如下:
  public class S_RoleMap : EntityTypeConfiguration<S_Role> {     public S_RoleMap()     {     // Primary Key  this.HasKey(t => t.ID);  // Properties  this.Property(t => t.RoleName).IsRequired().HasMaxLength(20);  this.Property(t => t.Remark).IsRequired().HasMaxLength(200);  // Table & Column Mappings  this.ToTable("S_Role");  this.Property(t => t.ID).HasColumnName("ID").HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);  this.Property(t => t.RoleName).HasColumnName("RoleName");  this.Property(t => t.Remark).HasColumnName("Remark");  // Relationships  this.HasMany(t => t.S_Menus)  .WithMany(t => t.S_Roles)  .Map(m =>  {      m.ToTable("S_RoleMenu");      m.MapLeftKey("RoleID");      m.MapRightKey("MenuID");  });     } } 
  1. 其他的设置在上面有说明呢,主要是Relationships,因为这里的S_Role和S_Menu的关系为多对多的关系,所以会产生一张关系表S_RoleMenu,并且是由RoleID和MenuID联合产生的主键,并且RoleID为S_Menu对象的外键,MenuID为S_Role的外键。
  • 接下来建立S_Menu的映射文件S_MenuMap,代码如下:
  public class S_MenuMap : EntityTypeConfiguration<S_Menu> {     public S_MenuMap()     {   this.HasKey(t => t.ID);  // Properties  this.Property(t => t.MenuName).IsRequired().HasMaxLength(20);  this.Property(t => t.Icon).IsRequired().HasMaxLength(20);  this.Property(t => t.Link).IsRequired().HasMaxLength(20);  this.Property(t => t.IsUse).IsOptional().HasMaxLength(2);  this.Property(t => t.Remark).IsOptional().HasMaxLength(200);  // Table & Column Mappings  this.ToTable("S_Menu");  this.Property(t => t.ID).HasColumnName("ID").HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);  this.Property(t => t.MenuName).HasColumnName("MenuName");  this.Property(t => t.Icon).HasColumnName("Icon");  this.Property(t => t.Link).HasColumnName("Link");  this.Property(t => t.IsUse).HasColumnName("IsUse");  this.Property(t => t.Level).HasColumnName("Level");  this.Property(t => t.SerialNO).HasColumnName("SerialNO");  this.Property(t => t.PID).HasColumnName("PID");  this.Property(t => t.Remark).HasColumnName("Remark");  // Relationships  this.HasOptional(t => t.Parent)   .WithMany(t => t.Children)   .HasForeignKey(d => d.PID);     } } 
  1. 在此关系中,PID为主键ID的外键,并且PID是为可空类型,因此外键的设定方式和RoleID的设定方式一样,只是把HasRequired变成了HasOptional,因为PID可以为空。
  • S_LogMap和S_TypeInfoMap就按照以上的方式创建就行呢。
  • 此示例中没有一对一的关系,特此我也把一对一的关系设定方式以一个示例写出来。

EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(四)

初始化数据库

  • 因为数据库是通过映射自动形成的,所以在数据库初始化的时候,我们给以为生成的表添加一些默认数据,比如默认的角色用户admin
  • 在工程Initializer下添加InitializerUserData类和DatabaseInitializer类,InitializerUserData类用来添加默认的角色用户。而DatabaseInitializer类负责对数据库的初始化,利用DbContext的Initialize来进行初始化。
  1. InitializerUserData的代码如下:
  public class InitializerUserData : CreateDatabaseIfNotExists<EFDbContext> {     protected override void Seed(EFDbContext context)     {  //添加默认角色  S_Role role = new S_Role();  role.ID = NewID.NewComb();  role.RoleName = "管理员";  role.Remark = "管理系统所有操作";  //添加默认用户  long RoleID = role.ID;  S_User user = new S_User();  user.ID = NewID.NewComb();  user.RoleID = RoleID;  user.UserName ="admin";  user.UserPwd=DESEncrypt.Encrypt("123");  user.IsUse="";  user.Phone="12345678901";  user.Email="demo@hotmail.com";  user.Remark = "系统管理员账户";  user.S_Role = role;  context.S_Roles.Add(role);  context.S_Users.Add(user);  context.SaveChanges();     } } 

2. DatabaseInitializer的代码如下:

  public static class DatabaseInitializer {  public static void Initialize()  {   Database.SetInitializer(new InitializerUserData());   using (var db = new EFDbContext())   {    db.Database.Initialize(false);   }  } } 

3. 注意:数据的初始化有三种方式,本示例选择CreateDatabaseIfNotExists,也就是如果数据库不存在我们就进行创建,如果数据库存在,

我们就只能通过数据迁移来进行对数据库的修改操作。

4. 在web工程的asp.net mvc 项目中的Global.asax添加对Initializer工程的引用,添加对数据库初始化操作的注册。如下显示:

  public class MvcApplication : System.Web.HttpApplication {     protected void Application_Start()     {  AreaRegistration.RegisterAllAreas();  WebApiConfig.Register(GlobalConfiguration.Configuration);  FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);  RouteConfig.RegisterRoutes(RouteTable.Routes);  BundleConfig.RegisterBundles(BundleTable.Bundles);  DatabaseInitializer.Initialize();     } } 
  • 运行WEB工程,我们就可以把实体映射生成数据库,打开数据库管理器查看如下:

EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(四)

备注

  • 到此,我们完成了POCO类的建立,以及通过Fluent API配置数据库,设置数据库初始化的值,完成了数据库的映射操作。如果要对实体进行修改重新映射到数据库,那么就要使用数据迁移,这个我就不多说了。
  • 完成的示例代码,我会放到网盘,不过目前的代码就是博文提到的,可以 点击下载 。如果是自己搭建的,可能会遇到EntityFramework程序集加载不正确的错误。原因是因为本地创建的MVC项目采用的是EntityFramework的5.0版本,而我们通过Nuget获取的是6.0版本,将工程集的EntityFramework5.0版本移除,重新加载6.0的就行呢。
正文到此结束
Loading...