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命令就可以安装了。

  

  

  • 在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就按照以上的方式创建就行呢。
  • 此示例中没有一对一的关系,特此我也把一对一的关系设定方式以一个示例写出来。

  

初始化数据库

  • 因为数据库是通过映射自动形成的,所以在数据库初始化的时候,我们给以为生成的表添加一些默认数据,比如默认的角色用户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="[email protected]";
            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的就行呢。
时间: 2024-08-07 00:17:18

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

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

前言 本系列源自对EF6 CodeFirst的探索,但后来发现在自己项目中构建的时候遇到了一些问题以及一些解决方法,因此想作为一个系列写下来. 本系列并不是教你怎么做架构设计,但可以参照一下里面的方法,EF系列大都采用DDD的构建方式,这也是目前最流行的.如果你想对DDD有所了解,可以在园子当中寻找那些DDD方面的文章或者是在CodePlex上下载相关的DDD设计模型源码进行研究. 如果你是一个新人或者是没有用过EF6 CodeFirst的人,那么本系列将带你一步一步构建自己的解决方案平台.如果

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

前言 在接下来的篇幅里将对系统的模块功能进行编写.主要以代码实现为主.这一篇我们需要完成系统模块“角色管理”的相关功能.完成后可以对系统框架结构有进一步了解. Abstract层 之前说过,Abstract层是对业务接口的定义,所以我们新建接口文件IS_UserRepository,定义增删改查业务的接口.这一层需要添加对Entities层的引用.代码如下: using Entities; using System.Collections.Generic; namespace Abstract

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

前言 在上一篇中,我们依靠着EasyUI强大的前端布局特性把前端登录界面和主界面给搭建完成了.这一篇我们就要尝试着把整个解决方案部署到云端呢,也就是Visual Studio Online(TFVC)中. 在我们进行团队项目开发的过程中,或多或少的都会接触到一些源代码管理工具,比如vss.svn.tfs.git,这些工具都有着自己的特点.但最终目的都是方便团队的协作开发,提高工作效率.Visual studio从2013版本开始就为我们提供了云端管理源代码的能力.Visual Studio On

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

前言 这一篇是本系列的最后一篇,虽然示例讲到这里就停止呢,但对于这些技术的学习远不能停止.虽然本示例讲的比较基础,但是正如我第一篇说到的,这个系列的目的不是说一些高端的架构设计,而是作为一个入门级,对学习EntityFramework6构建一个简单的示例以及对其进行设计,管理,编码的过程. 应部分园友要求,博客换了一个清爽的模板.之前的模板也是为了学习一下,所以按照其他模板的样式,把自己的博客园模板修该了一下.虽然这不是写博客的主要目的,但还是从中学习到了很多,比如小插件的应用.运行js和css

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

前言 上一篇文章我们完成了系统角色管理的基本功能实现,也对系统层次结构进行了了解.这一篇我们将继续对系统的用户管理模块进行代码编写.代码没有做封装,所以大部分的逻辑代码都是相通的,只是在一些前端的细节上处理有些不同.源码将在文章的末尾给出,有兴趣的园友可以对代码做一些封装或重构,毕竟这可以减少很多的代码量. Abstract层 在这一层添加对用户管理操作的业务接口IS_UserRepository,里面定义增删改查的业务接口.代码如下: using Entities; using System.

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

前言 今天早上,看到第一篇上首页呢,多谢支持! 写完第一篇后,我一直在想接下来应该从哪一方面开始讲.后来我觉得不用那么死板的把每一个课程和大纲都列出来吧,毕竟我又不是教书的,呵呵...我觉得就像做实验一样,我们一部分一部分的完成,最后总个结果应该就出来呢.那么这一篇就来把前端的样子弄出来,至少得知道长成什么样吧.接下来就应该开始捯饬了... 下载前端框架EasyUI 到EasyUI的官网下载压缩包.一个是基于GPL的开源版,一个是商业版.我们自己做研究就下个开源版呢.目前是1.4.3的版本,解压

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

前言 本篇幅将对系统的菜单管理模块进行说明,系统的菜单采用树形结构,这样可以更好地方便层级设计和查看.本示例将说明如何通过EntityFramework读取递归的菜单树形结构,以及结合EasyUI的treegrid在Asp.net MVC上显示树形菜单和管理操作. Easyui-treegrid的使用方法 首先我们来看一下treegrid的基本使用方法.很简单,和easyui-datagrid差不多. <table title="Folder Browser" class=&qu

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

前言 这一篇我们将完成系统的权限设置功能以及不同角色用户登录系统后动态加载菜单.注意:此示例权限只针对菜单级,如果园友需要更复杂的系统权限设置,可以拓展到按钮级或属性级. 用户的登录采用Form认证来实现,这样可以有效地防止非授权用户或页面链接对系统造成不安全的操作. 权限设置模块 页面采用角色列表和菜单列表勾选的方式,即选择角色后勾选可以操作的菜单,这样具有才角色的用户就具有操作这些菜单的权限.界面设置如下: 界面选择的roleID和menuID我们采用字符串的方式进行拼接.在controll

基于MVC4+EasyUI的Web开发框架形成之旅--MVC控制器的设计

自从上篇<基于MVC4+EasyUI的Web开发框架形成之旅--总体介绍>总体性的概括,得到很多同行的关注和支持,不过上一篇主要是介绍一个总体的界面效果和思路,本系列的文章将逐步介绍其中的细节,本文主要介绍整个Web开发框架中的MVC控制器的设计.在设计之初,我就希望尽可能的减少代码,提高编程模型的统一性.因此希望能够以基类继承的方式,和我Winform开发框架一样,尽可能通过基类,而不是子类的重复代码来实现各种通用的操作. 1.登录控制的控制器基类设计 我们知道,一般我们创建一个MVC的控制