系列導航地址http://www.cnblogs.com/fzrain/p/3490137.html
使用Entity Framework Code First模式構建數據庫對象
已經決定使用EF CodeFirst來創建數據庫了,因此我們使用POCO類(“Plain Old CLR Objects)來定義我們的Model。我們通過寫標准的.NET類來定義適合我們API的領域模型。那些POCO類就會為我們創建數據庫。
我們的培訓系統數據庫比較簡單,首先我們需要有學生”Students”,導師”Tutors”,除此之外我們還需要定義課程”Courses”以及科目”Subjects”。在我們的系統中,我們允許每個學生去報名參加不同的課程
下圖是我們數據庫建立后的結果,我在這里列出來的目的是為了幫助大家更好的理解接下來創建的POCO類(圖不是很清晰,湊合看吧):
第一步:創建一個空的類庫項目
打開VS,創建一個空的類庫項目
.NET Framewok版本的話我用的事4.5的,選4.0的也行
第二步:使用NuGet添加Entity Framework的引用
右擊引用->管理NuGet程序包->右上角輸入Entity Framework,選擇安裝。裝好之后就應該是這么一個樣子:
這里用的是Entity Framework6,因為這個版本支持了枚舉類型,在這次的項目里也有涉及
第三步:創建Model
前面已經說了,到目前為止我們還沒有數據庫。因此我們需要寫標准的.NET類來定義領域模型並幫我們生成數據庫
新建一個Entities文件夾並創建5個類(“Student”,“Course”,“Subject”,“Tutor”,“Enrollment”),這幾個簡單的實體類將為我們創建數據庫
public class Course { public Course() { Enrollments = new List<Enrollment>(); CourseTutor = new Tutor(); CourseSubject = new Subject(); } public int Id { get; set; } public string Name { get; set; } public Double Duration { get; set; } public string Description { get; set; } public Tutor CourseTutor { get; set; } public Subject CourseSubject { get; set; } public ICollection<Enrollment> Enrollments { get; set; } } public class Enrollment { public Enrollment() { Student = new Student(); Course = new Course(); } public int Id { get; set; } public DateTime EnrollmentDate { get; set; } public Student Student { get; set; } public Course Course { get; set; } } public class Student { public Student() { Enrollments = new List<Enrollment>(); } public int Id { get; set; } public string Email { get; set; } public string UserName { get; set; } public string Password { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public Gender Gender { get; set; } public DateTime DateOfBirth { get; set; } public DateTime? RegistrationDate { get; set; } public DateTime? LastLoginDate { get; set; } public ICollection<Enrollment> Enrollments { get; set; } } public class Subject { public Subject() { Courses = new List<Course>(); } public int Id { get; set; } public string Name { get; set; } public ICollection<Course> Courses; } public class Tutor { public Tutor() { Courses = new List<Course>(); } public int Id { get; set; } public string Email { get; set; } public string UserName { get; set; } public string Password { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public Gender Gender { get; set; } public ICollection<Course> Courses; }
不難發現,上面我們創建的Model並沒有從任何基類繼承,也沒有打任何attributes。有哪些標准的話會使我們的數據訪問擁有更好的延展性,這樣的話我們就可以專心地去處理業務而不用過多地考慮數據的持久化。
Entity framework Code First默認使用一種叫“約定大於配置”的方式來為我們創建數據庫(通過POCO類映射數據庫的表,字段的數據類型,外鍵等)。在一些簡單的應用程序中來說是非常好用的。但在我們的項目中將使用Fluent API配置我們自定義的數據庫生成規則——“配置覆蓋約定”
第四步:應用自定義映射規則
當我們決定使用配置來覆蓋默認規則時,我們可以為每張表的字段配置數據類型,是否可空,表與表之間外鍵關系,主鍵以及標識列等等。
於是乎我們創建一個“Mappers”文件夾,新建5個繼承自System.Data.Entity.ModelConfiguration.EntityTypeConfiguration<T>的類。分別命名為: “CourseMapper”, “EnrollmentMapper”, “StudentMapper”, “SubjectMapper”, and “TutorMapper”
class CourseMapper : EntityTypeConfiguration<Course> { public CourseMapper() { this.ToTable("Courses"); this.HasKey(c => c.Id); this.Property(c => c.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); this.Property(c => c.Id).IsRequired(); this.Property(c => c.Name).IsRequired(); this.Property(c => c.Name).HasMaxLength(255); this.Property(c => c.Duration).IsRequired(); this.Property(c => c.Description).IsOptional(); this.Property(c => c.Description).HasMaxLength(1000); this.HasRequired(c => c.CourseSubject).WithMany().Map(s => s.MapKey("SubjectID")); this.HasRequired(c => c.CourseTutor).WithMany().Map(t => t.MapKey("TutorID")); } } class EnrollmentMapper : EntityTypeConfiguration<Enrollment> { public EnrollmentMapper() { this.ToTable("Enrollments"); this.HasKey(e => e.Id); this.Property(e => e.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); this.Property(e => e.Id).IsRequired(); this.Property(e => e.EnrollmentDate).IsRequired(); this.Property(e => e.EnrollmentDate).HasColumnType("smalldatetime"); this.HasOptional(e => e.Student).WithMany(e => e.Enrollments).Map(s => s.MapKey("StudentID")).WillCascadeOnDelete(false); this.HasOptional(e => e.Course).WithMany(e => e.Enrollments).Map(c => c.MapKey("CourseID")).WillCascadeOnDelete(false); } } class StudentMapper : EntityTypeConfiguration<Student> { public StudentMapper() { this.ToTable("Students"); this.HasKey(s => s.Id); this.Property(s => s.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); this.Property(s => s.Id).IsRequired(); this.Property(s => s.Email).IsRequired(); this.Property(s => s.Email).HasMaxLength(255); this.Property(s => s.Email).IsUnicode(false); this.Property(s => s.UserName).IsRequired(); this.Property(s => s.UserName).HasMaxLength(50); this.Property(s => s.UserName).IsUnicode(false); this.Property(s => s.Password).IsRequired(); this.Property(s => s.Password).HasMaxLength(255); this.Property(s => s.FirstName).IsRequired(); this.Property(s => s.FirstName).HasMaxLength(50); this.Property(s => s.LastName).IsRequired(); this.Property(s => s.LastName).HasMaxLength(50); this.Property(s => s.Gender).IsOptional(); this.Property(s => s.DateOfBirth).IsRequired(); this.Property(s => s.DateOfBirth).HasColumnType("smalldatetime"); this.Property(s => s.RegistrationDate).IsOptional(); this.Property(s => s.RegistrationDate).HasColumnType("smalldatetime"); this.Property(s => s.LastLoginDate).IsOptional(); this.Property(s => s.LastLoginDate).HasColumnType("smalldatetime"); } } class SubjectMapper : EntityTypeConfiguration<Subject> { public SubjectMapper() { this.ToTable("Subjects"); this.HasKey(s => s.Id); this.Property(s => s.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); this.Property(s => s.Id).IsRequired(); this.Property(s => s.Name).IsRequired(); this.Property(s => s.Name).HasMaxLength(255); } } class TutorMapper : EntityTypeConfiguration<Tutor> { public TutorMapper() { this.ToTable("Tutors"); this.HasKey(s => s.Id); this.Property(s => s.Id).HasDatabaseGeneratedOption(DatabaseGeneratedOption.Identity); this.Property(s => s.Id).IsRequired(); this.Property(s => s.Email).IsRequired(); this.Property(s => s.Email).HasMaxLength(255); this.Property(s => s.Email).IsUnicode(false); this.Property(s => s.UserName).IsRequired(); this.Property(s => s.UserName).HasMaxLength(50); this.Property(s => s.UserName).IsUnicode(false); this.Property(s => s.Password).IsRequired(); this.Property(s => s.Password).HasMaxLength(255); this.Property(s => s.FirstName).IsRequired(); this.Property(s => s.FirstName).HasMaxLength(50); this.Property(s => s.LastName).IsRequired(); this.Property(s => s.LastName).HasMaxLength(50); this.Property(s => s.Gender).IsOptional(); } }
看看上面的代碼,不難發現我們為每個POCO類屬性(數據類型,是否為空,主鍵標識,外鍵關系)都做了配置。這些配置最終會影響我們數據庫中表的創建。
關於具體Fluent API的配置方法,可以參考:http://www.cnblogs.com/hyl8218/archive/2011/10/10/2205240.html
第五步:創建Context類來實現數據持久化
現在我們要創建一個LearningContext類繼承自System.Data.Entity.DbContext:
public class LearningContext : DbContext { public LearningContext() : base("eLearningConnection") { Configuration.ProxyCreationEnabled = false; Configuration.LazyLoadingEnabled = false; Database.SetInitializer(new MigrateDatabaseToLatestVersion<LearningContext, LearningContextMigrationConfiguration>()); } public DbSet<Course> Courses { get; set; } public DbSet<Enrollment> Enrollments { get; set; } public DbSet<Student> Students { get; set; } public DbSet<Subject> Subjects { get; set; } public DbSet<Tutor> Tutors { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Configurations.Add(new StudentMapper()); modelBuilder.Configurations.Add(new SubjectMapper()); modelBuilder.Configurations.Add(new TutorMapper()); modelBuilder.Configurations.Add(new CourseMapper()); modelBuilder.Configurations.Add(new EnrollmentMapper()); base.OnModelCreating(modelBuilder); } }
這個LearningContext類主要有三大任務
1.把我們定義的POCO類作為DBSet的屬性對外公開,這意味着每一個POCO類都將被轉化為數據庫中的表
2.重寫OnModelCreating方法將我們為POCO類自定義的映射規則添加到DbModelBuilder配置中
3.在LearningContext的構造函數中我們做了2件事:
(1)將ProxyCreationEnabled和LazyLoadingEnabled 2個屬性設為false(默認都是true)。延遲加載(LazyLoading)主要是指當對象間的關聯配置成導航屬性暴露給外界的時候,那么這個屬性的會在用到它的時候(在前台foreach的時候)才加載,那么極有可能成為性能殺手,我們項目中希望立即加載。而ProxyCreation是配合LazyLoading一起用的,因此當我們把這兩個屬性值設為false時,那么“LearningContext”就不會去加載導航屬性除非調用了“Include”方法(下一章會有演示)。
(2)為數據庫配置初始化和遷移策略,如果模型的屬性被改變(添加了新的屬性),就把數據庫遷移到最新版本。為了實現這個功能,我們新建一個類LearningContextMigrationConfiguration繼承自System.Data.Entity.Migrations.DbMigrationsConfiguration<TContext>
class LearningContextMigrationConfiguration : DbMigrationsConfiguration<LearningContext> { public LearningContextMigrationConfiguration() { this.AutomaticMigrationsEnabled = true; this.AutomaticMigrationDataLossAllowed = true; } #if DEBUG protected override void Seed(LearningContext context) { new LearningDataSeeder(context).Seed(); } #endif }
LearningContextMigrationConfiguration這個類主要有2個任務:
(1)在構造函數中,我們設置AutomaticMigrationsEnabled 屬性為true,那么就意味着EF會為我們自動遷移數據庫而不考慮版本問題。同時我們把AutomaticMigrationDataLossAllowed屬性也設為true但這樣做在開發環境中是很危險的,因為如果這個屬性設為false時,一旦數據庫在自動遷移時發生數據丟失,那么就會拋出一個異常。但在這次的系列中我們確保沒問題。
(2)重寫Seed方法來為我們的數據庫添加初始數據,這個函數在我們應用程序每次啟動時執行。“LearningDataSeeder”這個類主要用於提供需要的數據,具體代碼在本章結束時提供。
本章總結
到目前為止,我們已經把用於創建數據庫的Model和配置都實現了。在這個時候我們應該想一想:數據訪問層是否已經完成?當我們使用Web Api操作的時候是否方便 快捷 高效?我們是否應該對已有的數據訪問層再做一次封裝?。。。 因此下一章我們將使用“Repository”模式應用在我們的項目中。
隨堂代碼:http://yun.baidu.com/share/link?shareid=1763536438&uk=17559114