此Web应用程序演示如何使用Entity Framework 6和Visual Studio 2015创建ASP.NET MVC 5应用程序。本教程使用“Code First ”即代码先行。有关如何在“Code First”,“Database First”和“Model First”之间进行选择,请参阅实体框架开发工作流程。如下:
-
Database First
如果已经拥有数据库,Visual Studio中内置的Entity Framework设计器可以自动生成一个数据模型,该模型由对应于现有数据库对象(如表和列)的类和属性组成。有关数据库结构,数据模型及映射之间的信息以XML格式存储在.edmx文件中。实体框架设计器提供了一个可视化界面,您可以使用它来显示和编辑.edmx文件。
-
Model First
如果您还没有数据库,则可以使用Visual Studio中的Entity Framework设计器在.edmx文件中创建一个模型。当模型建完后,可以执行.edmx文件来创建数据库。
-
Code First
无论您是否拥有数据库,都可以使用Code First。如果没有数据库,可以编写类和对应于表和列的属性。如果有数据库,那么Entity Framework可以生成与现有表和列对应的类和属性。如果使用Code First创建数据库,则可以使用“migration(迁移)”来将数据库部署到生产环境。当数据模型更改时,可以将更改部署到生产环境中,而不改变原有的数据。
Contoso University Web应用程序
此教程中构建的应用程序是一个简单的web网站。
用户可以查看和更新学生课程和教师信息。以下是创建的几个页面。
创建一个MVC Web应用程序
打开Visual Studio 2015并创建一个名为“EFDemo”的新C# Web项目。
在“ 新建ASP.NET项目”对话框中,选择MVC模板。
更改身份验证,改为不进行身份验证(N)。
单击“ 确定”创建项目。
做几个简单的更改。打开视图\ Shared \ _Layout.cshtml,并进行以下更改,如下图出显示:
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 5 <meta charset="utf-8" /> 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 <title>@ViewBag.Title - Contoso University </title> 8 @Styles.Render("~/Content/css") 9 @Scripts.Render("~/bundles/modernizr") 10 </head> 11 <body> 12 <div class="navbar navbar-inverse navbar-fixed-top"> 13 <div class="container"> 14 <div class="navbar-header"> 15 <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse"> 16 <span class="icon-bar"></span> 17 <span class="icon-bar"></span> 18 <span class="icon-bar"></span> 19 </button> 20 @Html.ActionLink("Contoso University ", "Index", "Home", new { area = "" }, new { @class = "navbar-brand" }) 21 </div> 22 <div class="navbar-collapse collapse"> 23 <ul class="nav navbar-nav"> 24 <li>@Html.ActionLink("主页", "Index", "Home")</li> 25 <li>@Html.ActionLink("关于", "About", "Home")</li> 26 <li>@Html.ActionLink("学生", "Index", "Student")</li> 27 <li>@Html.ActionLink("课程", "Index", "Course")</li> 28 <li>@Html.ActionLink("老师", "Index", "Instructor")</li> 29 <li>@Html.ActionLink("部门", "Index", "Department")</li> 30 </ul> 31 </div> 32 </div> 33 </div> 34 <div class="container body-content"> 35 @RenderBody() 36 <hr /> 37 <footer> 38 <p>© @DateTime.Now.Year - Contoso University </p> 39 </footer> 40 </div> 41 42 @Scripts.Render("~/bundles/jquery") 43 @Scripts.Render("~/bundles/bootstrap") 44 @RenderSection("scripts", required: false) 45 </body> 46 </html>
在Views \ Home \ Index.cshtml中,使用以下代码替换原有内容:
1 @{ 2 ViewBag.Title = "Home Page"; 3 } 4 5 <div class="jumbotron"> 6 <h1>Contoso University </h1> 7 </div> 8 <div class="row"> 9 <div class="col-md-4"> 10 <h2>Welcome to Contoso University </h2> 11 <p> 12 Contoso University is a sample application that 13 demonstrates how to use Entity Framework 6 in an 14 ASP.NET MVC 5 web application. 15 </p> 16 </div> 17 <div class="col-md-4"> 18 <h2>Build it from scratch</h2> 19 <p>You can build the application by following the steps in the tutorial series on the ASP.NET site.</p> 20 <p><a class="btn btn-default" href="http://www.asp.net/mvc/tutorials/getting-started-with-ef-using-mvc/">See the tutorial »</a></p> 21 </div> 22 <div class="col-md-4"> 23 <h2>Download it</h2> 24 <p>You can download the completed project from the Microsoft Code Gallery.</p> 25 <p><a class="btn btn-default" href="http://code.msdn.microsoft.com/ASPNET-MVC-Application-b01a9fe8">Download »</a></p> 26 </div> 27 </div>
CTRL + F5运行网站,显示如下:
安装EF6
从工具菜单中单击NuGet包管理器,然后单击程序包管理器控制台。
在“ 管理器管理器”窗口中,输入以下命令:
Install-Package EntityFramework
第二种方法:项目上右击选择管理NuGet程序包,选择浏览输入EntityFramework搜索,列表就会列出结果,选择要安装的包,点击右侧的安装(我已经安装过了,所以显示的是"卸载")
这里显示安装的是Entity Framework6.1.3,NuGet将安装最新版本的Entity Framework(不包括预发行版本)。
创建数据模型
接下来,您将为Contoso University 应用程序创建实体。将从以下三个实体开始:
Student
和Enrollment是一对多关系
,Course
和Enrollment也是一对多的关系
。换句话说,学生可以注册任何数量的课程,课程可以被任何数量的学生注册(学生和课程是多对多关系)。
接下来将为每个实体创建一个类。
Student
在Models文件夹中,创建一个名为Student.cs的类文件,并使用以下代码替换模板代码:
1 using System; 2 using System.Collections.Generic; 3 4 namespace EFDemo.Models 5 { 6 public class Student 7 { 8 public int ID { get; set; } 9 public string LastName { get; set; } 10 public string FirstMidName { get; set; } 11 public DateTime EnrollmentDate { get; set; } 12 13 public virtual ICollection<Enrollment> Enrollments { get; set; } 14 } 15 }
该ID
属性将成为数据库表的主键列。默认情况下,Entity Framework将一个名为ID
或classnameID的属性作为主键。
该 Enrollments
属性是导航属性。导航属性包含与该实体相关的其他实体信息。在这种情况下,Enrollments
持有与该Student
实体相关的Enrollment
实体的所有信息。
导航属性通常被定义为virtual
使得它们可以利用某些实体框架功能,例如延迟加载。
如果导航属性包含多个实体(如多对多或一对多关系),则其类型必须是list集合,例如ICollection
。
Enrollment
在Models文件夹中,创建Enrollment.cs并使用以下代码替换现有代码:
1 namespace EFDemo.Models 2 { 3 public enum Grade 4 { 5 A, B, C, D, F 6 } 7 8 public class Enrollment 9 { 10 public int EnrollmentID { get; set; } 11 public int CourseID { get; set; } 12 public int StudentID { get; set; } 13 public Grade? Grade { get; set; } 14 15 public virtual Course Course { get; set; } 16 public virtual Student Student { get; set; } 17 } 18 }
该EnrollmentID
属性是主键; 该实体主键使用类名 加ID的命名方式
,而不是直接使用ID
。通常会选择一种固定的命名方式,并在所有数据模型中使用。在这里,可以使用任意命名方式。在后面的教程中,将看到如何使用ID,
而不用classnameID,这样
更容易在数据模型中实现继承。
该Grade
属性是一个枚举类型。Grade
类型后的问号表示该Grade
属性可以为空。空值表示未知等级或尚未分配。
该StudentID
属性是一个外键,以及相应的导航属性Student
。一个Enrollment
实体与一个Student
实体相关联,因此该属性只能保存一个Student
实体。
该CourseID
属性也是一个外键,以及相应的导航属性Course
。一个Enrollment
实体与一个Course
实体相关联。
Course
在Models文件夹中,创建Course.cs,使用以下代码替换模板代码:
1 using System.Collections.Generic; 2 using System.ComponentModel.DataAnnotations.Schema; 3 4 namespace EFDemo.Models 5 { 6 public class Course 7 { 8 [DatabaseGenerated(DatabaseGeneratedOption.None)] 9 public int CourseID { get; set; } 10 public string Title { get; set; } 11 public int Credits { get; set; } 12 13 public virtual ICollection<Enrollment> Enrollments { get; set; } 14 } 15 }
该Enrollments
属性是导航属性。一个Course
实体可以有任意数量的Enrollment
实体。
DatabaseGenerated属性,DatabaseGeneratedOption.None 设置允许输入课程主键,而不是让数据库自动生成它(不是自动增长)。
创建数据库上下文
右键单击该项目解决方案资源管理器,然后单击新建文件夹。命名新文件夹DAL(用于数据访问层)。在该文件夹中创建一个名为SchoolContext.cs的类,代码如下:
1 using EFDemo.Models; 2 using System.Data.Entity; 3 using System.Data.Entity.ModelConfiguration.Conventions; 4 5 namespace EFDemo.DAL 6 { 7 public class SchoolContext : DbContext 8 { 9 public SchoolContext() : base("SchoolDbContext") 10 { 11 12 } 13 14 public DbSet<Student> Students { get; set; } 15 public DbSet<Enrollment> Enrollments { get; set; } 16 public DbSet<Course> Courses { get; set; } 17 18 protected override void OnModelCreating(DbModelBuilder modelBuilder) 19 { 20 //阻止表名复数形式 21 modelBuilder.Conventions.Remove<PluralizingTableNameConvention>(); 22 } 23 } 24 }
指定连接字符串
连接字符串的名称传递给构造函数
1 public SchoolContext() : base("SchoolDbContext") 2 { 3 }
Web.config文件中连接字符串如以下所示:
1 <connectionStrings> 2 <add name="SchoolDbContext" providerName="System.Data.SqlClient" connectionString="Data Source=ServerName;Initial Catalog=DatabaseName;Integrated Security=False;User Id=userid;Password=password;MultipleActiveResultSets=True" /> 3 </connectionStrings>
也可以传递连接字符串本身,而不是存储在Web.config文件中。
如果不指定连接字符串或显式指定名称,则Entity Framework假定连接字符串名称与类名称相同。
指定实体集DbSet<>
每个实体集创建一个DbSet属性。在EF中,实体集对应于数据库的表,实体对应于表中的一行。
注意:可以省略声明DbSet<Enrollment>
和DbSet<Course>
,它们将正常工作。EF将默认隐式地包含它们。
指定单数表名
OnModelCreating 方法中的modelBuilder.Conventions.Remove
语句阻止表名的复数形式。如果不这样做,在数据库中生成的表将被命名为复数形式(Students)。
设置使用测试数据来初始化数据库
EF可以在应用程序运行时(或者当模型与现有数据库不同步时)自动创建(或删除或重新创建)数据库。还可以编写一个 Seed 方法,EF将在创建数据库后自动调用,以便添加测试数据。
EF默认的是在数据库不存在(CreateDatabaseIfNotExists)时创建数据库(如果模型已更改,数据库已经存在),则抛出异常。在本章中,将指定每当模型改变时,才删除和重新创建数据库。删除和重新创建数据库会丢失数据库中原先的所有数据。这通常只在开发过程中使用,因为Seed方法将在数据库重新创建时才运行,并重新添加测试数据。但在生产中环境,不可能每次更改数据库时丢失所有数据。在下面的章节,将学习如何通过使用Code First migration(迁移)来更改数据库,而不影响原来的数据。
CreateDatabaseIfNotExists
这是默认的数据库初始化类,除非手动指定其他类。顾名思义,CreateDatabaseIfNotExists类仅在不存在数据库时创建数据库。
DropCreateDatabaseIfModelChanges
只要当模型类和表之间不匹配时,就会删除数据库并重新创建它。
DropCreateDatabaseAlways
每次运行应用程序时,都将删除并重新创建数据库,而不管它是否已经存在。
在DAL文件夹中,创建一个名为SchoolInitializer.cs的类,代码如下:
1 using EFDemo.Models; 2 using System; 3 using System.Collections.Generic; 4 using System.Data.Entity; 5 6 namespace EFDemo.DAL 7 { 8 public class SchoolInitializer : DropCreateDatabaseIfModelChanges<SchoolContext> 9 { 10 protected override void Seed(SchoolContext context) 11 { 12 var students = new List<Student> 13 { 14 new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-01")}, 15 new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")}, 16 new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")}, 17 new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")}, 18 new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")}, 19 new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")}, 20 new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")}, 21 new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")} 22 }; 23 students.ForEach(s => context.Students.Add(s)); 24 context.SaveChanges(); 25 26 var courses = new List<Course> 27 { 28 new Course{CourseID=1050,Title="Chemistry",Credits=3,}, 29 new Course{CourseID=4022,Title="Microeconomics",Credits=3,}, 30 new Course{CourseID=4041,Title="Macroeconomics",Credits=3,}, 31 new Course{CourseID=1045,Title="Calculus",Credits=4,}, 32 new Course{CourseID=3141,Title="Trigonometry",Credits=4,}, 33 new Course{CourseID=2021,Title="Composition",Credits=3,}, 34 new Course{CourseID=2042,Title="Literature",Credits=4,} 35 }; 36 courses.ForEach(s => context.Courses.Add(s)); 37 context.SaveChanges(); 38 39 var enrollments = new List<Enrollment> 40 { 41 new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A}, 42 new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C}, 43 new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B}, 44 new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B}, 45 new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F}, 46 new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F}, 47 new Enrollment{StudentID=3,CourseID=1050}, 48 new Enrollment{StudentID=4,CourseID=1050,}, 49 new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F}, 50 new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C}, 51 new Enrollment{StudentID=6,CourseID=1045}, 52 new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A}, 53 }; 54 enrollments.ForEach(s => context.Enrollments.Add(s)); 55 context.SaveChanges(); 56 } 57 } 58 }
该Seed方法用于添加测试数据。
指定数据库初始化程序
打开Web.config文件,添加代码如下:
1 <entityFramework> 2 3 <contexts> 4 <context type="EFDemo.DAL.SchoolContext, EFDemo"> 5 <databaseInitializer type="EFDemo.DAL.SchoolInitializer, EFDemo" /> 6 </context> 7 </contexts> 8 9 <defaultConnectionFactory type="System.Data.Entity.Infrastructure.LocalDbConnectionFactory, EntityFramework"> 10 <parameters> 11 <parameter value="mssqllocaldb" /> 12 </parameters> 13 </defaultConnectionFactory> 14 <providers> 15 <provider invariantName="System.Data.SqlClient" type="System.Data.Entity.SqlServer.SqlProviderServices, EntityFramework.SqlServer" /> 16 </providers> 17 </entityFramework>
或者
1 <appSettings> 2 <add key = "DatabaseInitializerForType EFDemo.DAL.SchoolContext,EFDemo" value = "EFDemo.DAL.SchoolInitializer,EFDemo" /> 3 </appSettings>
它必须以预定义值DatabaseInitializerForType开头,后跟一个空格,然后是上下文类的全名称。
在context节点,type
指定上下文类的全名称,后面是程序集的名称。当你不想EF使用初始化,您可以给context
元素设置一个属性:disableDatabaseInitialization="true"
。
作为在Web.config文件中设置初始化程序的替代方法是在Global.asax.cs文件中通过执行代码来设置:
1 protected void Application_Start() 2 { 3 AreaRegistration.RegisterAllAreas(); 4 FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters); 5 RouteConfig.RegisterRoutes(RouteTable.Routes); 6 BundleConfig.RegisterBundles(BundleTable.Bundles); 7 8 //指定数据库初始化程序 9 Database.SetInitializer(new SchoolInitializer()); 10 }
SetInitializer() 方法接受数据库初始化程序类的实例,并将其设置为当前应用程序数据库初始化程序。设置数据库初始化程序时,不会立即调用。当第一次使用上下文(SchoolContext)时,它将被调用。
当首次访问数据库时,Entity Framework会将数据库与模型进行比较,如果有差异,将删除并重新创建数据库。
如果在数据库初始化时,不想使用延迟加载,可以使用Initialize()方法覆盖。如果模型复杂且初始化需要很多时间,可以显式调用数据库初始化程序。这样就可以在某个已知步骤执行初始化(并且可以向用户显示等待消息)。使用Initialize()方法,代码如下所示:
1 Database.Initialize(false);//立刻初始化数据,不延迟加载
在上述代码中,数据库将在调用Initialize()方法后立即创建,而不是延迟加载。Initialize()方法接受一个布尔类型的参数,用于控制是否重新执行初始化。指定false,如果已经执行,则跳过初始化过程。指定true,将重新初始化,即使它已被初始化。
有时可能不希望执行数据库初始化逻辑。在这种情况下,可以通过将null传递给SetInitializer()方法,则将跳过初始化。
1 Database.SetInitializer<SchoolContext>(null);
注意:如果将应用程序部署到生产环境,则必须删除或禁用初始化代码。
创建自定义数据库初始化程序
在上述示例中,使用内置的数据库初始化程序。还可以通过实现IDatabaseInitializer<>接口来创建自定义数据库初始化程序。需要实现IDatabaseInitializer<>接口的InitializeDatabase()方法,并编写自己的数据库创建逻辑。以下代码显示了InitializeDatabase()方法的示例实现:
1 public class SchoolInitializer : IDatabaseInitializer<SchoolContext> 2 { 3 #region 继承 IDatabaseInitializer<SchoolContext>接口,实现InitializeDatabase()方法 4 public void InitializeDatabase(SchoolContext context) 5 { 6 //判断数据库是否已经存在 7 if (context.Database.Exists()) 8 { 9 //数据库模式是否与模型兼容 10 if (!context.Database.CompatibleWithModel(true)) 11 { 12 context.Database.Delete(); 13 } 14 } 15 context.Database.Create(); 16 //使用自定义初始值设置来执行自定义任务 17 context.Database.ExecuteSqlCommand("CREATE TABLE CS_DATA([Id] int identity(1,1) primary key, [Name] NVARCHAR(50) not null)"); 18 } 19 #endregion 20 }
注意:代码使用ExecuteSqlCommand()方法来创建不属于模型的表。虽然我们在示例中不使用该表,但它说明了如何使用自定义初始化设置来执行自定义任务。
创建Student控制器和视图
现在,将创建一个页面来显示数据,请求数据的过程将自动触发数据库的创建。先创建一个新的控制器。但是在这之前,需要重新生成整个项目,因为我们将使用MVC脚手架自动生成CRUD页面。
- 右键单击解决方案资源管理器中的Controllers文件夹,选择添加,然后单击控制器。
-
在弹出的对话框中,选择包含视图的MVC 5控制器(使用Entity Framework)。
-
在“添加控制器”对话框中,进行以下选择,然后单击“ 添加”:
- 模特类:学生(Student(EFDemo.Models))。(如果在下拉列表中没有看到此选项,则重新生成项目,然后重试)
- 数据上下文类:SchoolContext(EFDemo.DAL)。
- 控制器名称:StudentController。
-
其他保留默认值。
当单击添加时,将创建一个StudentController.cs文件和一组与控制器配合使用的视图(.cshtml文件)。
4.Visual Studio打开Controllers \ StudentController.cs文件。您看到已经创建了一个实例化数据库上下文对象的类变量:
1 private SchoolContext db = new SchoolContext();
该Index
操作方法通过数据库上下文实例的属性查询出所有学生信息:
1 // GET: Student 2 public ActionResult Index() 3 { 4 return View(db.Students.ToList()); 5 }
该学生\ Index.cshtml视图显示此列表中的表:
1 @model IEnumerable<EFDemo.Models.Student> 2 3 @{ 4 ViewBag.Title = "Index"; 5 } 6 7 <h2>Index</h2> 8 9 <p> 10 @Html.ActionLink("Create New", "Create") 11 </p> 12 <table class="table"> 13 <tr> 14 <th> 15 @Html.DisplayNameFor(model => model.LastName) 16 </th> 17 <th> 18 @Html.DisplayNameFor(model => model.FirstMidName) 19 </th> 20 <th> 21 @Html.DisplayNameFor(model => model.EnrollmentDate) 22 </th> 23 <th></th> 24 </tr> 25 26 @foreach (var item in Model) { 27 <tr> 28 <td> 29 @Html.DisplayFor(modelItem => item.LastName) 30 </td> 31 <td> 32 @Html.DisplayFor(modelItem => item.FirstMidName) 33 </td> 34 <td> 35 @Html.DisplayFor(modelItem => item.EnrollmentDate) 36 </td> 37 <td> 38 @Html.ActionLink("Edit", "Edit", new { id=item.ID }) | 39 @Html.ActionLink("Details", "Details", new { id=item.ID }) | 40 @Html.ActionLink("Delete", "Delete", new { id=item.ID }) 41 </td> 42 </tr> 43 } 44 45 </table>
5.按CTRL + F5运行项目。(或Index.cshtml文件右击在浏览器中查看或者直接按F5)
单击“ 学生”选项卡以查看Seed()
方法插入的测试数据。
查看数据库
当您运行学生页面,应用程序尝试访问数据库时,EF看到没有数据库,因此它创建了一个数据库,然后运行Seed()方法添加测试数据。
因为正在使用DropCreateDatabaseIfModelChanges
初始化程序,现在可以对Student
类进行更改,再次运行应用程序,将自动重新创建数据库。例如,如果向Student
该类添加一个属性EmailAddress
,再次运行“学生”页面,然后再次查看该表,将看到一个新列EmailAddress
。
1 public string EmailAddress { get; set; }
当重新运行时,可能会报这个错:
解决办法:先删除原数据库,勾选关闭现有连接,然后再运行程序。后面利用迁移就不需要手动删除数据库了。
然后查看数据库,增加了新的列
约定
使用Entity Framework能够创建一个完整的数据库,发现我们只编写了很少的代码,因为Entity Framework有默认约定:
- 实体类名一般用作表名。
- 实体属性名称用于列名。
- 名称
ID
或classnameID属性被识别为主键属性。 - 还有外键属性,例如,
StudentID
用于Student
导航属性。
当然我们可以将属性显式地标记为外键属性等。将在本系列后面继续学习。
参考:https://docs.microsoft.com/zh-cn/aspnet/