新聞中心
一、前言

成都創(chuàng)新互聯(lián)公司公司2013年成立,先為德化等服務(wù)建站,德化等地企業(yè),進行企業(yè)商務(wù)咨詢服務(wù)。為德化企業(yè)網(wǎng)站制作PC+手機+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。
經(jīng)過EF的《第一篇》,我們已經(jīng)把數(shù)據(jù)訪問層基本搭建起來了,但并沒有涉及實體關(guān)系。實體關(guān)系對于一個數(shù)據(jù)庫系統(tǒng)來說至關(guān)重要,而且EF的各個實體之間的聯(lián)系,實體之間的協(xié)作,聯(lián)合查詢等也都依賴于這些實體關(guān)系。
二、實體映射
實體與數(shù)據(jù)庫的映射可以通過DataAnnotation與FluentAPI兩種方式來進行映射:
(一) DataAnnotation
DataAnnotation 特性由.NET 3.5中引進,給.NET中的類提供了一種添加驗證的方式。DataAnnotation由命名空間System.ComponentModel.DataAnnotations提供。下面列舉實體模型中常用的DataAnnotation特性:
- KeyAttribute:對應(yīng)數(shù)據(jù)庫中的主鍵
- RequiredAttribute:對應(yīng)數(shù)據(jù)庫中字段的數(shù)據(jù)是否可以為null
- MaxLengthAttribute:對應(yīng)數(shù)據(jù)庫中字符串類型字段的最大長度
- MinLengthAttribute:在數(shù)據(jù)庫中無對應(yīng),但在代碼中字符串最小長度
- ConcurrencyCheckAttribute:指定用于開放式并發(fā)檢查的列的數(shù)據(jù)類型
- TimestampAttribute:將列的數(shù)據(jù)類型指定為行版本
System.ComponentModel.DataAnnotations命名空間中只定義了部分實體驗證的特性,在EntityFramework程序集中定義了更多的數(shù)據(jù)映射特性:
- DatabaseGeneratedAttribute:標記指定實體屬性是由數(shù)據(jù)庫生成的,并指定生成策略(None數(shù)據(jù)庫不生成值,Identity當(dāng)插入行時,數(shù)據(jù)庫生成值,Computed當(dāng)插入或更新行時,數(shù)據(jù)庫生成值)
- ColumnAttribute:指定實體屬性在數(shù)據(jù)庫中的列名及數(shù)據(jù)類型
- TableAttribute:指定實體類對應(yīng)的數(shù)據(jù)表名
- ForeignKeyAttribute:指定導(dǎo)航屬性的外鍵字段
- NotMappedAttribute:標記指定實體屬性在創(chuàng)建數(shù)據(jù)庫中不創(chuàng)建對應(yīng)字段
- ComplexTypeAttribute:標記指定實體屬性是將一個對象作為另一個對象的屬性,映射到數(shù)據(jù)庫中則子對象表現(xiàn)為多個屬性字段
對于實體關(guān)系對應(yīng)的數(shù)據(jù)表關(guān)系,無非“0:1,1:1,0:N,1:N,N:N”這幾種,可以使用導(dǎo)航屬性中的數(shù)據(jù)類型來表示,0…1端使用單實體類型表示,N端使用ICollection
(二) Fluent API
使用DataAnnotation非常簡單,但對于EntityFramework中的特性,就要在實體類中引入EntityFramework程序集,但實體類最好能是保持與架構(gòu)無關(guān)性的POCO類,才能更具通用性。所以,最好是在數(shù)據(jù)層中使用FluentAPI在數(shù)據(jù)層中進行實體類與數(shù)據(jù)庫之間的映射工作。
當(dāng)然,System.ComponentModel.DataAnnotations命名空間的DataAnnotation在EntityFramework程序集中也有相應(yīng)的API:
- HasKey - KeyAttribute:配置此實體類型的主鍵屬性
- IsRequired - RequiredAttribute:將此屬性配置為必需屬性。用于存儲此屬性的數(shù)據(jù)庫列將不可以為null
- HasMaxLength - MaxLengthAttribute:將屬性配置為具有指定的最大長度
- IsConcurrencyToken - ConcurrencyCheckAttribute:將屬性配置為用作開放式并發(fā)標記
- IsRowVersion - TimestampAttribute:將屬性配置為數(shù)據(jù)庫中的行版本。實際數(shù)據(jù)類型將因使用的數(shù)據(jù)庫提供程序而異。將屬性設(shè)置為行版本會自動將屬性配置為開放式并發(fā)標記。
上面這些API均無需引用EntityFramework,推薦使用DataAnnotation方式來設(shè)置映射。
以下API的DataAnnotation特性是在EntityFramework中定義,如果也使用DataAnnotation方式來設(shè)置映射,就會給實體類增加額外的第三方程序集的依賴。所以以下API的映射推薦使用FluentAPI的方式來設(shè)置映射:
- ToTable - TableAttribute:配置此實體類型映射到的表名
- HasColumnName - ColumnAttribute:配置用于存儲屬性的數(shù)據(jù)庫列的名稱
- HasForeignKey - ForeignKeyAttribute:將關(guān)系配置為使用在對象模型中的外鍵屬性。如果未在對象模型中公開外鍵屬性,則使用Map方法
- Ignore - NotMappedAttribute:從模型中排隊某個屬性,使該屬性不會映射到數(shù)據(jù)庫
- HasRequired:通過此實體類型配置必需關(guān)系。除非指定此關(guān)系,否則實體類型的實例將無法保存到數(shù)據(jù)庫。數(shù)據(jù)庫中的外鍵不可為null。
- HasOptional:從此實體類型配置可選關(guān)系。實體類型的實例將能保存到數(shù)據(jù)庫,而無需指定此關(guān)系。數(shù)據(jù)庫中的外鍵可為null。
- HasMany:從此實體類型配置一對多關(guān)系。
- WithOptional:將關(guān)系配置為required:optional。(required:0…1端的1,表示必需,不可為null;optional:0…1端的0,表示可選,可為null。下同)
- WithOptionalDependent:將關(guān)系配置為optional:optional。要配置的實體類型將成為依賴對象,且包含主體的外鍵。作為關(guān)系目標的實體類型將成為關(guān)系中的主體。
- WithOptionalPrincipal:將關(guān)系配置為optional:optional。要配置的實體類型將成為關(guān)系中的主體。作為關(guān)系目標的實體類型將成為依賴對象,且包含主體的外鍵。
- WithRequired:將關(guān)系的指定端配置為必需的,且在關(guān)系的另一端有導(dǎo)航屬性。
- WithRequiredDependent:將關(guān)系配置為required:required。要配置的實體類型將成為依賴對象,且包含主體的外鍵。作為關(guān)系目標的實體類型將成為關(guān)系中的主體。
- WithRequiredPrincipal:將關(guān)系配置為required:required。要配置的實體類型將成為關(guān)系中的實體。作為關(guān)系目標的實體類型將成為依賴對象,且包含主體的外鍵。
- WillCascadeOnDelete:配置是否對關(guān)系啟用級聯(lián)刪除。
- Map:將關(guān)系配置為使用未在對象模型中公開的外鍵屬性??赏ㄟ^指定配置操作來自定義列和表。如果指定了空的配置操作,則約定將生成列名。如果在對象模型中公開了外鍵屬性,則使用 HasForeignKey 方法。并非所有關(guān)系都支持在對象模型中公開外鍵屬性。
- MapKey:配置外鍵的列名。
- ToTable:配置外鍵列所在表的名稱和架構(gòu)。
經(jīng)常用到的DataAnnotation與FluentAPI列舉完了,使用上還是遵守這個原則:
如果在System.ComponentModel.DataAnnotations命名空間存在相應(yīng)的標簽,就使用 DataAnnotation 的方式,如果不存在,則使用 FluentAPI 的方式。
(三) 映射代碼示例
實體類關(guān)系圖:
上圖是一個以用戶信息為中心的實體關(guān)系圖,關(guān)系說明如下:
- 一個用戶可擁有一個可選的用戶擴展信息(1 - 0)
- 一個用戶擴展信息擁有一個必需的所屬用戶信息(0 - 1)
- 一個用戶擴展信息擁有一個用戶地址信息(復(fù)合類型)
- 一個用戶可對應(yīng)多個登錄日志信息(1 - N)
- 一個登錄日志擁有一個必需的所屬用戶信息(N- 1)
- 一個用戶可以擁有多個角色(N - N)
- 一個角色可以分配給多個用戶(N - N)
實體類定義:
用戶信息
- namespace GMF.Demo.Core.Models
- {
- ///
- /// 實體類——用戶信息
- ///
- [Description("用戶信息")]
- public class Member : Entity
- {
- public int Id { get; set; }
- [Required]
- [StringLength(20)]
- public string UserName { get; set; }
- [Required]
- [StringLength(32)]
- public string Password { get; set; }
- [Required]
- [StringLength(20)]
- public string NickName { get; set; }
- [Required]
- [StringLength(50)]
- public string Email { get; set; }
- ///
- /// 獲取或設(shè)置 用戶擴展信息
- ///
- public virtual MemberExtend Extend { get; set; }
- ///
- /// 獲取或設(shè)置 用戶擁有的角色信息集合
- ///
- public virtual ICollection
Roles { get; set; } - ///
- /// 獲取或設(shè)置 用戶登錄記錄集合
- ///
- public virtual ICollection
LoginLogs { get; set; } - }
- }
用戶擴展信息
- namespace GMF.Demo.Core.Models
- {
- ///
- /// 實體類——用戶擴展信息
- ///
- [Description("用戶擴展信息")]
- public class MemberExtend : Entity
- {
- ///
- /// 初始化一個 用戶擴展實體類 的新實例
- ///
- public MemberExtend()
- {
- Id = CombHelper.NewComb();
- }
- public Guid Id { get; set; }
- public string Tel { get; set; }
- public MemberAddress Address { get; set; }
- public virtual Member Member { get; set; }
- }
- }
用戶地址信息
- namespace GMF.Demo.Core.Models
- {
- ///
- /// 用戶地址信息
- ///
- public class MemberAddress
- {
- [StringLength(10)]
- public string Province { get; set; }
- [StringLength(20)]
- public string City { get; set; }
- [StringLength(20)]
- public string County { get; set; }
- [StringLength(60, MinimumLength = 5)]
- public string Street { get; set; }
- }
- }
登錄記錄信息
- namespace GMF.Demo.Core.Models
- {
- ///
- /// 實體類——登錄記錄信息
- ///
- [Description("登錄記錄信息")]
- public class LoginLog : Entity
- {
- ///
- /// 初始化一個 登錄記錄實體類 的新實例
- ///
- public LoginLog()
- {
- Id = CombHelper.NewComb();
- }
- public Guid Id { get; set; }
- [Required]
- [StringLength(15)]
- public string IpAddress { get; set; }
- ///
- /// 獲取或設(shè)置 所屬用戶信息
- ///
- public virtual Member Member { get; set; }
- }
- }
角色信息
- namespace GMF.Demo.Core.Models
- {
- ///
- /// 實體類——角色信息
- ///
- [Description("角色信息")]
- public class Role : Entity
- {
- public Role()
- {
- Id = CombHelper.NewComb();
- }
- public Guid Id { get; set; }
- [Required]
- [StringLength(20)]
- public string Name { get; set; }
- [StringLength(100)]
- public string Description { get; set; }
- ///
- /// 獲取或設(shè)置 角色類型
- ///
- public RoleType RoleType { get; set; }
- ///
- /// 獲取或設(shè)置 角色類型的數(shù)值表示,用于數(shù)據(jù)庫存儲
- ///
- public int RoleTypeNum { get; set; }
- ///
- /// 獲取或設(shè)置 擁有此角色的用戶信息集合
- ///
- public virtual ICollection
Members { get; set; } - }
- }
角色類型(枚舉)
- namespace GMF.Demo.Core.Models
- {
- ///
- /// 表示角色類型的枚舉
- ///
- [Description("角色類型")]
- public enum RoleType
- {
- ///
- /// 用戶類型
- ///
- [Description("用戶角色")]
- User = 0,
- ///
- /// 管理員類型
- ///
- [Description("管理角色")]
- Admin = 1
- }
- }
#p#
實體類映射:
實體類映射中,關(guān)系的映射配置在關(guān)系的兩端都可以配置。例如,用戶信息與登錄信息的 一對多 關(guān)系可以在用戶信息端配置:
HasMany(m => m.LoginLogs).WithRequired(n => n.Member);
等效于在登錄日志信息端配置:
HasRequired(m => m.Member).WithMany(n => n.LoginLogs);
但是,如果所有的關(guān)系映射都在作為主體的用戶信息端進行配置,勢必造成用戶信息端配置的臃腫與職責(zé)不明。所以,為了保持各個實體類型的職責(zé)單一,實體關(guān)系推薦在關(guān)系的非主體端進行映射。
用戶信息映射類,用戶信息是關(guān)系的主體,所有的關(guān)系都不在此映射類中進行配置
- namespace GMF.Demo.Core.Data.Configurations
- {
- public class MemberConfiguration : EntityTypeConfiguration
- {
- }
- }
用戶擴展信息映射類,配置用戶擴展信息與用戶信息的 0:1 關(guān)系
- namespace GMF.Demo.Core.Data.Configurations
- {
- public class MemberExtendConfiguration : EntityTypeConfiguration
- {
- public MemberExtendConfiguration()
- {
- HasRequired(m => m.Member).WithOptional(n => n.Extend);
- }
- }
- }
用戶地址信息映射類,配置用戶地址信息的復(fù)雜類型映射,復(fù)雜類型繼承于 ComplexTypeConfiguration<>
- namespace GMF.Demo.Core.Data.Configurations
- {
- public class MemberAddressConfiguration : ComplexTypeConfiguration
- {
- public MemberAddressConfiguration()
- {
- Property(m => m.Province).HasColumnName("Province");
- Property(m => m.City).HasColumnName("City");
- Property(m => m.County).HasColumnName("County");
- Property(m => m.Street).HasColumnName("Street");
- }
- }
- }
登錄記錄信息映射,配置登錄信息與用戶信息的 N:1 的關(guān)系
- namespace GMF.Demo.Core.Data.Configurations
- {
- public class LoginLogConfiguration : EntityTypeConfiguration
- {
- public LoginLogConfiguration()
- {
- HasRequired(m => m.Member).WithMany(n => n.LoginLogs);
- }
- }
- }
角色信息映射,配置角色信息與用戶信息的 N:N 的關(guān)系
- namespace GMF.Demo.Core.Data.Configurations
- {
- public class RoleConfiguration : EntityTypeConfiguration
- {
- public RoleConfiguration()
- {
- HasMany(m => m.Members).WithMany(n => n.Roles);
- }
- }
- }
映射類的應(yīng)用:
映射類需要在數(shù)據(jù)訪問上下文中進行應(yīng)用才能生效,只要在DbContext的OnModelCreating方法中進行映射配置添加即可。
- protected override void OnModelCreating(DbModelBuilder modelBuilder)
- {
- //移除一對多的級聯(lián)刪除約定,想要級聯(lián)刪除可以在 EntityTypeConfiguration
的實現(xiàn)類中進行控制 - modelBuilder.Conventions.Remove
(); - //多對多啟用級聯(lián)刪除約定,不想級聯(lián)刪除可以在刪除前判斷關(guān)聯(lián)的數(shù)據(jù)進行攔截
- //modelBuilder.Conventions.Remove
(); - modelBuilder.Configurations.Add(new MemberConfiguration());
- modelBuilder.Configurations.Add(new MemberExtendConfiguration());
- modelBuilder.Configurations.Add(new MemberAddressConfiguration());
- modelBuilder.Configurations.Add(new RoleConfiguration());
- modelBuilder.Configurations.Add(new LoginLogConfiguration());
- }
#p#
三、數(shù)據(jù)遷移
經(jīng)過上面的折騰,數(shù)據(jù)庫結(jié)構(gòu)已經(jīng)大變,項目當(dāng)然運行不起來了。
根據(jù)提示,必須進行遷移來更新數(shù)據(jù)庫結(jié)構(gòu)。EntityFramework的數(shù)據(jù)遷移通過 NuGet 來進行。打開程序包管理器控制臺(Package Manager Console),鍵入“ get-help EntityFramework”命令,可以獲得相關(guān)的幫助信息。
若想了解各個子命令的幫助細節(jié),也可鍵入“get-help 子命令”命令,例如:get-help Enable-Migrations
下面我們來對項目進行數(shù)據(jù)遷移,在我們的項目中,EntityFramework的依賴止于項目GMF.Demo.Core.Data,項目的數(shù)據(jù)遷移也是在此項目中進行。遷移步驟如下:
- 在“程序包管理器控制臺”鍵入命令:Enable-Migrations -ProjectName GMF.Demo.Core.Data
- 添加后,項目中添加了一個名為Migrations的文件夾
添加生成以下代碼:
- namespace GMF.Demo.Core.Data.Migrations
- {
- internal sealed class Configuration : DbMigrationsConfiguration
- {
- public Configuration()
- {
- AutomaticMigrationsEnabled = false;
- }
- protected override void Seed(DemoDbContext context)
- {
- // This method will be called after migrating to the latest version.
- // You can use the DbSet
.AddOrUpdate() helper extension method - // to avoid creating duplicate seed data. E.g.
- //
- // context.People.AddOrUpdate(
- // p => p.FullName,
- // new Person { FullName = "Andrew Peters" },
- // new Person { FullName = "Brice Lambson" },
- // new Person { FullName = "Rowan Miller" }
- // );
- //
- }
- }
- }
方法Seed中可以進行數(shù)據(jù)遷移后的數(shù)據(jù)初始化工作,將在每次遷移之后運行。如上代碼所示,AddOrUpdate是IDbSet
還有一個名為InitialCreate的類,配置生成數(shù)據(jù)庫的細節(jié):
InitialCreate
- namespace GMF.Demo.Core.Data.Migrations
- {
- public partial class InitialCreate : DbMigration
- {
- public override void Up()
- {
- CreateTable(
- "dbo.Roles",
- c => new
- {
- Id = c.Guid(nullable: false),
- Name = c.String(nullable: false, maxLength: 20),
- Description = c.String(maxLength: 100),
- IsDeleted = c.Boolean(nullable: false),
- AddDate = c.DateTime(nullable: false),
- Timestamp = c.Binary(nullable: false, fixedLength: true, timestamp: true, storeType: "rowversion"),
- })
- .PrimaryKey(t => t.Id);
- CreateTable(
- "dbo.Members",
- c => new
- {
- Id = c.Int(nullable: false, identity: true),
- UserName = c.String(nullable: false, maxLength: 20),
- Password = c.String(nullable: false, maxLength: 32),
- NickName = c.String(nullable: false, maxLength: 20),
- Email = c.String(nullable: false, maxLength: 50),
- IsDeleted = c.Boolean(nullable: false),
- AddDate = c.DateTime(nullable: false),
- Timestamp = c.Binary(nullable: false, fixedLength: true, timestamp: true, storeType: "rowversion"),
- })
- .PrimaryKey(t => t.Id);
- CreateTable(
- "dbo.MemberExtends",
- c => new
- {
- Id = c.Guid(nullable: false),
- IsDeleted = c.Boolean(nullable: false),
- AddDate = c.DateTime(nullable: false),
- Timestamp = c.Binary(nullable: false, fixedLength: true, timestamp: true, storeType: "rowversion"),
- Member_Id = c.Int(nullable: false),
- })
- .PrimaryKey(t => t.Id)
- .ForeignKey("dbo.Members", t => t.Member_Id)
- .Index(t => t.Member_Id);
- CreateTable(
- "dbo.LoginLogs",
- c => new
- {
- Id = c.Guid(nullable: false),
- IpAddress = c.String(nullable: false, maxLength: 15),
- IsDeleted = c.Boolean(nullable: false),
- AddDate = c.DateTime(nullable: false),
- Timestamp = c.Binary(nullable: false, fixedLength: true, timestamp: true, storeType: "rowversion"),
- Member_Id = c.Int(),
- })
- .PrimaryKey(t => t.Id)
- .ForeignKey("dbo.Members", t => t.Member_Id)
- .Index(t => t.Member_Id);
- CreateTable(
- "dbo.MemberRoles",
- c => new
- {
- Member_Id = c.Int(nullable: false),
- Role_Id = c.Guid(nullable: false),
- })
- .PrimaryKey(t => new { t.Member_Id, t.Role_Id })
- .ForeignKey("dbo.Members", t => t.Member_Id, cascadeDelete: true)
- .ForeignKey("dbo.Roles", t => t.Role_Id, cascadeDelete: true)
- .Index(t => t.Member_Id)
- .Index(t => t.Role_Id);
- }
- public override void Down()
- {
- DropIndex("dbo.MemberRoles", new[] { "Role_Id" });
- DropIndex("dbo.MemberRoles", new[] { "Member_Id" });
- DropIndex("dbo.LoginLogs", new[] { "Member_Id" });
- DropIndex("dbo.MemberExtends", new[] { "Member_Id" });
- DropForeignKey("dbo.MemberRoles", "Role_Id", "dbo.Roles");
- DropForeignKey("dbo.MemberRoles", "Member_Id", "dbo.Members");
- DropForeignKey("dbo.LoginLogs", "Member_Id", "dbo.Members");
- DropForeignKey("dbo.MemberExtends", "Member_Id", "dbo.Members");
- DropTable("dbo.MemberRoles");
- DropTable("dbo.LoginLogs");
- DropTable("dbo.MemberExtends");
- DropTable("dbo.Members");
- DropTable("dbo.Roles");
- }
- }
- }
- 執(zhí)行“Add-Migration FirstMigration”命令,添加一個名為FirstMigration的遷移
- 執(zhí)行“Update-Database”命令,更新數(shù)據(jù)庫架構(gòu)
如果更新數(shù)據(jù)庫存在沖突而不能執(zhí)行更新,可以添加 -Force強制執(zhí)行,例如:“Update-Database -Force”
- 設(shè)置自動遷移
每次都通過控制臺來進行遷移太過麻煩,可以設(shè)置為自動遷移。
有以下兩個參數(shù)可以對自動遷移進行設(shè)置:
1. AutomaticMigrationsEnabled:獲取或設(shè)置 指示遷移數(shù)據(jù)庫時是否可使用自動遷移的值。
2. AutomaticMigrationDataLossAllowed:獲取或設(shè)置 指示是否可接受自動遷移期間的數(shù)據(jù)丟失的值。如果設(shè)置為false,則將在數(shù)據(jù)丟失可能作為自動遷移一部分出現(xiàn)時引發(fā)異常。
修改遷移的Configuration類如下:
- namespace GMF.Demo.Core.Data.Migrations
- {
- internal sealed class Configuration : DbMigrationsConfiguration
- {
文章名稱:EF Code First:實體映射,數(shù)據(jù)遷移,重構(gòu)
網(wǎng)頁網(wǎng)址:http://m.5511xx.com/article/cophjcp.html


咨詢
建站咨詢
