前言
- 這一篇,我們終於到了講解Entity Framework CodeFirst 的時刻了,首先創建實體對象模型,然后會通過配置Fluent API的方式來對實體對象模型進行完整的數據庫映射操作。
- 此篇幅中會涉及到一些Entity Frame的相關概念,會給出初步的解釋。如果需要詳細了解,可以查閱相關的幫助文檔。
EF實體對象模型的創建
- EF的實體對象模型大都采用POCO類的方式創建。POCO的全稱為Plain_Old_CLR_Object(簡單傳統CLR對象),是指那些沒有從任何類繼承,也沒有實現任何接口的簡單對象。EF CodeFirst可以利用POCO類創建的實體對象來對數據庫進行映射,即我們可以通過編寫POCO類中的屬性和關聯POCO類的關系的方式完成對數據庫的映射操作。
- 我們知道數據庫的規范通過范式來進行約束,那我們如何通過POCO類的方式來完成對數據庫映射以及約束的操作呢?首先我們得把實體對象間的關系創建清楚,實體對象間的關系有三種:一對一、一對多、多對多。接下來我們通過完成示例來演示如何創建這些示例。
- 打開解決方案的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可以對數據庫進行詳細的配置,主要包括:
- 主鍵的設置
- 屬性的設置
- 表和字段的設置
- 關系的設置
- 在采用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); } }
- S_UserMap繼承了EntityTypeConfiguration<S_User>的泛型類
- HasKey用來指定那個屬性為主鍵
- 在Properties設置中,IsRequired用來設定屬性為必須字段,不可為空。HasMaxLength用來設置字段的長度。IsOptional用來設定屬性為可選字段,可以為空。
- ToTable可以用來設置表的名稱,HasColumnName用來設定字段的名稱。如果你想數據庫字段名和實體類中的屬性名不一樣,可以在此進行設置。HasDatabaseGeneratedOption用來表示字段列是否為自增長,本示例中,我們的主鍵采用的long類型的日期流水碼,不需要字段編號。所以設置為none。
- 在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"); }); } }
- 其他的設置在上面有說明呢,主要是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); } }
- 在此關系中,PID為主鍵ID的外鍵,並且PID是為可空類型,因此外鍵的設定方式和RoleID的設定方式一樣,只是把HasRequired變成了HasOptional,因為PID可以為空。
- S_LogMap和S_TypeInfoMap就按照以上的方式創建就行呢。
- 此示例中沒有一對一的關系,特此我也把一對一的關系設定方式以一個示例寫出來。

初始化數據庫
- 因為數據庫是通過映射自動形成的,所以在數據庫初始化的時候,我們給以為生成的表添加一些默認數據,比如默認的角色用戶admin
- 在工程Initializer下添加InitializerUserData類和DatabaseInitializer類,InitializerUserData類用來添加默認的角色用戶。而DatabaseInitializer類負責對數據庫的初始化,利用DbContext的Initialize來進行初始化。
- 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工程,我們就可以把實體映射生成數據庫,打開數據庫管理器查看如下:

備注
- 到此,我們完成了POCO類的建立,以及通過Fluent API配置數據庫,設置數據庫初始化的值,完成了數據庫的映射操作。如果要對實體進行修改重新映射到數據庫,那么就要使用數據遷移,這個我就不多說了。
- 完成的示例代碼,我會放到網盤,不過目前的代碼就是博文提到的,可以點擊下載。如果是自己搭建的,可能會遇到EntityFramework程序集加載不正確的錯誤。原因是因為本地創建的MVC項目采用的是EntityFramework的5.0版本,而我們通過Nuget獲取的是6.0版本,將工程集的EntityFramework5.0版本移除,重新加載6.0的就行呢。
