前言

  • 这一篇,我们终于到了讲解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命令就可以安装了。

  

  

  • 在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();
this.Property(t => t.UserPwd).IsRequired().HasMaxLength();
this.Property(t => t.IsUse).IsRequired().HasMaxLength();
this.Property(t => t.Phone).IsOptional().HasMaxLength();
this.Property(t => t.Email).IsOptional().HasMaxLength();
this.Property(t => t.Remark).IsOptional().HasMaxLength(); // 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();
this.Property(t => t.Remark).IsRequired().HasMaxLength(); // 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();
this.Property(t => t.Icon).IsRequired().HasMaxLength();
this.Property(t => t.Link).IsRequired().HasMaxLength();
this.Property(t => t.IsUse).IsOptional().HasMaxLength();
this.Property(t => t.Remark).IsOptional().HasMaxLength(); // 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就按照以上的方式创建就行呢。
  • 此示例中没有一对一的关系,特此我也把一对一的关系设定方式以一个示例写出来。

  

初始化数据库

  • 因为数据库是通过映射自动形成的,所以在数据库初始化的时候,我们给以为生成的表添加一些默认数据,比如默认的角色用户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("");
user.IsUse="是";
user.Phone="";
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工程,我们就可以把实体映射生成数据库,打开数据库管理器查看如下:

  

备注

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

EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(四)的更多相关文章

  1. EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(一)

    前言 本系列源自对EF6 CodeFirst的探索,但后来发现在自己项目中构建的时候遇到了一些问题以及一些解决方法,因此想作为一个系列写下来. 本系列并不是教你怎么做架构设计,但可以参照一下里面的方法 ...

  2. EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(六)

    前言 在接下来的篇幅里将对系统的模块功能进行编写.主要以代码实现为主.这一篇我们需要完成系统模块“角色管理”的相关功能.完成后可以对系统框架结构有进一步了解. Abstract层 之前说过,Abstr ...

  3. EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(完)

    前言 这一篇是本系列的最后一篇,虽然示例讲到这里就停止呢,但对于这些技术的学习远不能停止.虽然本示例讲的比较基础,但是正如我第一篇说到的,这个系列的目的不是说一些高端的架构设计,而是作为一个入门级,对 ...

  4. EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(八)

    前言 本篇幅将对系统的菜单管理模块进行说明,系统的菜单采用树形结构,这样可以更好地方便层级设计和查看.本示例将说明如何通过EntityFramework读取递归的菜单树形结构,以及结合EasyUI的t ...

  5. EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(七)

    前言 上一篇文章我们完成了系统角色管理的基本功能实现,也对系统层次结构进行了了解.这一篇我们将继续对系统的用户管理模块进行代码编写.代码没有做封装,所以大部分的逻辑代码都是相通的,只是在一些前端的细节 ...

  6. EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(三)

    前言 在上一篇中,我们依靠着EasyUI强大的前端布局特性把前端登录界面和主界面给搭建完成了.这一篇我们就要尝试着把整个解决方案部署到云端呢,也就是Visual Studio Online(TFVC) ...

  7. EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(二)

    前言 写完第一篇后,我一直在想接下来应该从哪一方面开始讲.后来我觉得不用那么死板的把每一个课程和大纲都列出来吧,毕竟我又不是教书的,呵呵...我觉得就像做实验一样,我们一部分一部分的完成,最后总个结果 ...

  8. EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(九)

    前言 这一篇我们将完成系统的权限设置功能以及不同角色用户登录系统后动态加载菜单.注意:此示例权限只针对菜单级,如果园友需要更复杂的系统权限设置,可以拓展到按钮级或属性级. 用户的登录采用Form认证来 ...

  9. EF6 CodeFirst+Repository+Ninject+MVC4+EasyUI实践(五)

    前言 在编写代码的时候,我遇到了很多关于EntityFramework6的疑问,所以现在就提前把这些问题列出来做一下解答,以便在以后的代码编写过程中减少不必要的Bug. EntityFramework ...

随机推荐

  1. 网易测试分享会——“一起打造你想要的QA团队”

    昨天(2016.11.30)参加了网易资深测试专家王晓明的测试分享会——“一起打造你想要的QA团队”,以下为笔者做的归纳总结. 重点 1.让测试更加容易做好.不容易测试的代码,不具有健壮性. 2.Ke ...

  2. Socket TCP之keepalive

    摘自: http://machael.blog.51cto.com/829462/211989/

  3. laravel的学习感受

    学习laravel也有一段时间了自己感觉还是不怎么难就是自己的代码不熟悉所以很多的不怎么会.

  4. TomTom (16Q3)数据协议基础

    title: TomTom数据协议介绍 title: TomTom数据协议介绍TomTom公司介绍主要经营的业务TomTom数据特点数据内容概述数据表命名特点数据关联特点数据的基本信息 TomTom公 ...

  5. Reactjs的Controller View模式

    摘要:做一个可以利用props来控制和传递所有状态给其子组件的顶级组件是一件非常酷的事情 不要和“MVC”混淆了,只有能够控制和传递所有的“state”的顶层组件,我们才叫它"view co ...

  6. [原创]Keil uVision4 安装破解

    Keil uVision4 安装没什么特别的,跟一般Windows软件安装方法相同,这里不再赘述. 安装完成界面如下图,未破解之前,点击编译,下载等选项都会出现未响应提示,软件会卡死.下面进行软件破解 ...

  7. ASP.NET MVC 静态资源打包和压缩问题小记

    ASP.NET MVC 中有个 BundleConfig 用于静态资源的打包和压缩,我在使用的过程中遇到一些问题,现在做下总结,并给出具体的解决方案. 问题一:打包压缩后的 JavaScript 和 ...

  8. linux文件权限查看及修改(实用)

    查看Linux文件的权限:ls -l 文件名称 查看linux文件夹的权限:ls -ld 文件夹名称(所在目录) 修改文件及文件夹权限: sudo chmod -(代表类型)×××(所有者)×××(组 ...

  9. Git 使用教程

    Git 使用教程 更详细请参考:廖雪峰的官方网站 - Git教程 1. 安装Git客户端软件 Git for Windows http://msysgit.github.io/ 2. 创建版本库 两点 ...

  10. phantomjs+selenium实现爬取动态网址

    之前使用 selenium + firefox驱动浏览器来实现爬取动态网址,但是firefox经常更新,更新后时常会导致webdriver启动不来,所以改用phantomjs+selenium来改善一 ...