基于Asp.Net Core Mvc和EntityFramework Core 的实战入门教程系列-2

来个目录吧:
第一章-入门
第二章- Entity Framework Core Nuget包管理
第三章-建立、修改、删除、查询
第四章-排序、过滤、分页、分组
第五章-迁移,EF Core 的codefirst使用
暂时就这么多。后面陆续更新吧html

Entity Framework Core Nuget包管理

若是你建立项目的时候启用了我的身份验证的话,项目中就已经包含了EFCore的支持。
若是你是单纯的空项目想将EFCore添加到你的项目中话,你须要安装一下的Nuget程序包:mysql

vs2017能够直接进行编辑项目的.csproj文件,安装所需软件包。
···





···
(您能够编辑的.csproj文件右击解决方案资源管理器中的项目名称并选择编辑 ContosoUniversity.csproj)。编程

我又来了,我亲测了下.NETCORE1.1目前不支持Microsoft.EntityFrameworkCore.Tools.DotNet这样玩,这里先略过json

建立数据模型

建立Contoso大学实体前,说下他们的关联关系吧。数组

Paste_Image.png

Student 和Enrollment 的实体关系为一对多。
Course和Enrollment的实体关系一样为一对多。安全

简单来讲就是一个学生能够参加任意一门课程,而一门课程能够有不少个学生。(固然同一个课程该学生只能参加一次,反之亦然)服务器

而后咱们开始建立实体吧。

Student 实体

Paste_Image.png

咱们在根目录新建一个“Models”文件夹,建立一个“Student”的类文件,复制如下代码替换掉内容。

using System;
using System.Collections.Generic;
namespace ContosoUniversity.Models
{
    public class Student
    {
        public int ID { get; set; }
        public string LastName { get; set; }
        public string FirstMidName { get; set; }
        public DateTime EnrollmentDate { get; set; }
        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

Id 属性将做为Student类对应的数据库表的主键,默认状况下EF框架都会将ID或者classnameID做为主键。

Enrollments属性是一个导航属性。

老外翻译太绕了,导航属性就是方便你从一个对象导航到关联对象,也能够用于设置对象之间的关联。

这里Enrollments就是Student实体的导航属性,能够经过Enrollments属性将实体Enrollment和Student中的关联信息获取出来。换句话说:一个学生在数据库中有2行登记信息,(每行包含了Student实体的ID),那么该Student能够从导航属性Enrollments中获取到2行登记信息。

若是一个导航属性包含了多个实体(如:一对多,多对多的关系),那么他们的类型必须一个list类型,能够添加、删除、修改。好比:ICollection<T> 。固然你还能够声明为List<T>或者HashSet<T>.若是你声明为ICollection<T> .EF会默认建立类型为`HashSet<T>

Enrollment 实体

Paste_Image.png

一样在Models文件中,建立一个类“Enrollment” 而后把代码替换为以下:

namespace ContosoUniversity.Models
{
    public enum Grade
    {
        A, B, C, D, F
    }

    public class Enrollment
    {
        public int EnrollmentID { get; set; }
        public int CourseID { get; set; }
        public int StudentID { get; set; }
        public Grade? Grade { get; set; }

        public Course Course { get; set; }
        public Student Student { get; set; }
    }
}

咱们将EnrollmentID做为Enrollment的主键,使用的是classnameID而不是像Student实体中的ID。这里暂时不解释,后面会提到这个问题。

咱们声明了一个枚举Grade属性。而在Enrollment实体中Grade是个可空类型,说明他是一个默承认觉得空的值,能够在后面根据具体的业务状况来进行赋值处理。

StudentID 做为属性外键,对应的导航属性为Student。ENrollment和Student的关联关系为一对一,因此Enrollment只能持有一个Student实体。(而Student拥有了Enrollment的多个导航属性)

CourseID做为Course的导航属性外键。一样的Enrollment和Course也是一对一的关系。

在这里StudentID做为Student导航属性的外键,等同于Student实体中的ID主键

Course 实体

Paste_Image.png

在Models文件中,建立一个Course类,而后替换为以下代码:

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class Course
    {
        [DatabaseGenerated(DatabaseGeneratedOption.None)]
        public int CourseID { get; set; }
        public string Title { get; set; }
        public int Credits { get; set; }

        public ICollection<Enrollment> Enrollments { get; set; }
    }
}

在这里Enrollments做为导航属性,一个课程会有多个不一样学生的登记信息,因此是一对多的关系。

建立数据库上下文(Database Context)

咱们须要建立一个做为EF框架用来链接数据库上下文的类。咱们建立的类是从System.Data.Entity.DbContext中派生出来的。你能够定义哪些实体包含在EF的数据模型中。你也能够自定义特定的EF行为。在这个项目中,咱们建立一个类名“SchoolContext”

  • 在根目录建立一个文件夹“Data”。
  • 在“Data”这个文件夹中,建立一个新的类“SchoolContext”,而后替换代码为下面:
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;

namespace ContosoUniversity.Data
{
    public class SchoolContext : DbContext
    {
        public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
        {
        }

        public DbSet<Course> Courses { get; set; }
        public DbSet<Enrollment> Enrollments { get; set; }
        public DbSet<Student> Students { get; set; }
    }
}

咱们为每一个实体都建立了一个DbSet的属性。在EF框架中,实体集一般对应数据库中的表,一个实体对应表中的一行数据。

在这里你能够忽略掉DbSet and DbSet ,它一样会生成表。由于Student会引用Enrollment实体,而Enrollment中包含了Course实体。一样被会引用。EF在生成表的时候会包含他们的引用实体。

建立数据库的时候,数据库的表名会跟 DbSet的属性名一致。属性名称一般会是复数(如 student的表名是Students),可是大多数开发者不一样意将表名和实体名字区分开,这样容易混淆。下面的教程就会教会你怎么经过指定你个性化的DbContext表名
把下面的代码,复制到最后的DbSet属性下面

protected override void OnModelCreating(ModelBuilder modelBuilder)
        {
            modelBuilder.Entity<Course>().ToTable("Course");
            modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
            modelBuilder.Entity<Student>().ToTable("Student");
        }

使用依赖注入的方式来注入DbContext

ASP.NET Core默认实现了依赖注入。在程序启动的时候使用依赖注入将服务(EF的数据库上下文)注入。这些服务的组件(如MVC控制器)经过构造函数的参数实现,下面咱们会逐步实现。

首先咱们打开“Startup.cs”类,而后将“SchoolContext”注入到ConfigureServices方法中。

services.AddDbContext<SchoolContext>(options =>
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

链接字符串的名称是经过DbContextOptionsBuilder对象的方法进行上下文调用的。

而在ASP.NET CORE中的链接字符串是经过“appsettings.json”文件实现的。
以下代码:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;MultipleActiveResultSets=true"
  },
  "Logging": {
    "IncludeScopes": false,
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

SQL Server Express LocalDB

上面的连接字符串说下吧,链接字符串指定SQL Server LocalDB数据库。LocalDB是一个轻量级版本的SQL Server Express数据库引擎,用于开发环境、而不是生产。LocalDB开始于需求和运行在用户模式下,因此没有复杂的配置。默认状况下,LocalDB建立。

mdf数据库文件在C:/用户/ wer_ltm 文件夹中
我确定不是这样干的。咱们修改下连接字符串

Data Source=.; Database=MaterialCirculation; User ID=sa; Password=123;

初始化数据库而且添加一些测试数据到数据库中

EF框架默认生成的数据库 是一个空数据库,为了咱们的测试、开发方便咱们添加一些测试数据到数据库中。

这里咱们使用EnsureCreated方法来自动建立数据库。在后面的教程中,咱们会经过Codefirst迁移的方式来修改数据库而不是传统的删除并从新建立一个数据库的方式来改变模型架构。

在"Data"文件夹中,建立一个“DbInitializer”类,而后把现有代码替换为如下代码:

using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Models;

namespace ContosoUniversity.Data
{
    public static class DbInitializer
    {
        public static void Initialize(SchoolContext context)
        {
            context.Database.EnsureCreated();

            // Look for any students.
            if (context.Students.Any())
            {
                return;   // DB has been seeded
            }

            var students = new Student[]
            {
            new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-01")},
            new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")},
            new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")},
            new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")},
            new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")}
            };
            foreach (Student s in students)
            {
                context.Students.Add(s);
            }
            context.SaveChanges();

            var courses = new Course[]
            {
            new Course{CourseID=1050,Title="Chemistry",Credits=3,},
            new Course{CourseID=4022,Title="Microeconomics",Credits=3,},
            new Course{CourseID=4041,Title="Macroeconomics",Credits=3,},
            new Course{CourseID=1045,Title="Calculus",Credits=4,},
            new Course{CourseID=3141,Title="Trigonometry",Credits=4,},
            new Course{CourseID=2021,Title="Composition",Credits=3,},
            new Course{CourseID=2042,Title="Literature",Credits=4,}
            };
            foreach (Course c in courses)
            {
                context.Courses.Add(c);
            }
            context.SaveChanges();

            var enrollments = new Enrollment[]
            {
            new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
            new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
            new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
            new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
            new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
            new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
            new Enrollment{StudentID=3,CourseID=1050},
            new Enrollment{StudentID=4,CourseID=1050,},
            new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
            new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
            new Enrollment{StudentID=6,CourseID=1045},
            new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
            };
            foreach (Enrollment e in enrollments)
            {
                context.Enrollments.Add(e);
            }
            context.SaveChanges();
        }
    }}

上面的代码会先检查数据库是否有任何的学生信息,若是没有的话,他会假定数据库须要新的测试种子数据,这里选择了将数据添加到数组中,没有选择List 集合来进行性能的优化。

在Startup.cs中,修改Configure 中的方法,以便程序在启动的时候调用Seed方法。

  • 首先修改Configure 方法,添加构造参数"SchoolContext "到方法中,这样ASP.NET 的依赖注入能够提供服务给DbInitializer类。
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, SchoolContext context)
{
    loggerFactory.AddConsole(Configuration.GetSection("Logging"));
    loggerFactory.AddDebug();

添加代码** DbInitializer.Initialize(context);方法,在整个Configure**方法的最下面。

app.UseMvc(routes =>
    {
        routes.MapRoute(
            name: "default",
            template: "{controller=Home}/{action=Index}/{id?}");
    });

    DbInitializer.Initialize(context);//记得添加这行
}

如今,当你第一次运行程序的时候会给你建立测试数据。每当你更改实体的时候,也就是数据模型的时候,能够删除数据库。更新种子数据并从新建立一个新的数据库。在之后的教程中,你会学会如何经过codefirst的方式经过迁移的方式来进行数据的修改和建立。

建立一个控制器和视图

接下来,咱们使用visual studio 中脚手架功能,添加MVC的控制器和视图,将使用EF的查询和保存数据。

经过脚手架功能,咱们能够自动建立一个CRUD的功能。你能够经过修改脚手架生成的代码来知足你的业务要求。当你的类发生变化的时候,你能够经过脚手架从新生成代码。

在VS2017 脚手架被叫作了基架 ,在我看来同样的难听。。。

  • 右键选择“Controllers”文件夹,而后选择添加>新搭基建项目
  • 在对话框中,
    • 选择“视图使用EntityFramework的MVC控制器”
      -- 点击添加
  • 添加控制器对话框中
    -- 模型类:选择Student
    -- 数据上下文类:选择 SchoolContext
    -- 接收默认的StudentController做为名称
    -- 点击添加

Paste_Image.png

当你点击“添加的时候”,VS 基架引擎会自动生成一个“StudentController.cs”文件和一组视图文件(.cshtml)。

打开StudentController控制器,你会发现SchoolContext 做为了构造函数的参数。

namespace ContosoUniversity.Controllers
{
    public class StudentsController : Controller
    {
        private readonly SchoolContext _context;

        public StudentsController(SchoolContext context)
        {
            _context = context;
        }

咱们以前在“Startup.cs”中已经配置了依赖注入。如今
ASP.NET会经过依赖注入的方式,将SchoolContext 注入到控制器中。

控制器中包含了一个Index的Action 方法,它会将数据库中的全部学生信息都显示出来。

**await _context.Students.ToListAsync()** 方法会从Student实体经过读取数据库上下文属性获取学生列表。

public async Task<IActionResult> Index()
{
    return View(await _context.Students.ToListAsync());
}

这里是采用了异步方法。咱们在后面讲解

咱们先打开“Views/Students/Index.cshtml ”视图文件:

@model IEnumerable<ContosoUniversity.Models.Student>

@{
    ViewData["Title"] = "Index";
}

<h2>Index</h2>

<p>
    <a asp-action="Create">Create New</a>
</p>
<table class="table">
    <thead>
        <tr>
                <th>
                    @Html.DisplayNameFor(model => model.LastName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.FirstMidName)
                </th>
                <th>
                    @Html.DisplayNameFor(model => model.EnrollmentDate)
                </th>
            <th></th>
        </tr>
    </thead>
    <tbody>
@foreach (var item in Model) {
        <tr>
            <td>
                @Html.DisplayFor(modelItem => item.LastName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.FirstMidName)
            </td>
            <td>
                @Html.DisplayFor(modelItem => item.EnrollmentDate)
            </td>
            <td>
                <a asp-action="Edit" asp-route-id="@item.ID">Edit</a> |
                <a asp-action="Details" asp-route-id="@item.ID">Details</a> |
                <a asp-action="Delete" asp-route-id="@item.ID">Delete</a>
            </td>
        </tr>
}
    </tbody>
</table>

好了 按 CTRL + F5 运行项目或选择调试 > 开始执行(不调试)。

在菜单上选择Student按钮。而后就能够看到咱们的数据信息。

Paste_Image.png

查看数据库

若是你是改了链接字符串的话,直接打开数据库看表吧。若是你没有更改,那就不要跳过这里了。

咱们刚刚说过了,你若是采用的是免费版本的话,如今要查看数据库就须要打开
工具-链接数据库

Paste_Image.png

展开表信息,而后选择Student 而后右键表,点击查看数据。

Paste_Image.png

公约

因为EF框架的公约/设定,为了让EntityFramework可以为您建立一个完整的数据库,你只须要编写不多的代码。

  • DbSet 属性的名称做为表的名称。对于不是由DbSet属性引用的实体,实体类名将做为表的名称
  • 实体属性名称会做为表的列名称
  • 名为ID或者classnameID的实体属性会被识别为主键属性
  • 若是属性被命名,属性则会被EF做为外键属性。(例如:StudentID对于Student导航属性,由于Student实体的主键为ID)。外键属性也能够随意命名。(例如:EnrollmentID由于Enrollment实体的主键是EnrollmentID)

一些常规的设定是能够进行覆盖的。例如你能够显示指定表的名称,就如咱们以前作的,自定义表的名称。
固然你也能够设置列名称并将任何属性设置为主键或者外键。在后面的教程中咱们会涉及。

关于异步代码

异步编程是ASP.NET Core和EF Core的默认模式。

Web服务器的线程数量是有限的,在高负载的状况下,可能全部的线程都被占用了。当发生这种状况的时候,服务器不能处理新的请求,直到线程被释放出来。再之前使用同步代码请求,许多线程可能被绑定,他们实际上没有作任何工做,由于他们正在等待I/O完成。使用异步写代码,当进程等待I/O完成的时候,它的线程就会被释放,服务器用于处理其余请求。所以,异步代码使服务器资源可以更有效地使用,而且服务器可以无延迟地处理更多流量。

异步代码在运行时引入少许开销,可是对于低流量状况,性能命中是能够忽略的,而对于高流量状况,潜在的性能改进是巨大的。

在如下代码中,async关键字,Task 返回值,await关键字和ToListAsync方法使代码异步执行。

public async Task<IActionResult> Index()
{
    return View(await _context.Students.ToListAsync());
}
  • 该async关键字告诉编译器生成方法不会回调和自动建立Task 返回的对象。
  • 返回类型Task 表示正在进行的工做与类型的结果IActionResult。
  • 该await关键字使编译器将该方法拆分为两部分。第一部分以异步启动的操做结束。第二部分被放入一个在操做完成时被调用的回调方法中。
  • ToListAsync是ToList扩展方法的异步版本。

当你使用EntityFramework的异步代码的时候,你须要注意一些事情:

  • 只有查询或者发送命令到数据库的时候才能使用异步语句。如:ToListAsync,SingleOrDefaultAsync,和SaveChangesAsync。不包含 类型为IQueryable,修改命令。好比
var students = context.Students.Where(s => s.LastName == "Davolio").
  • EF的上下文不是线程安全的:不要尝试执行/并行多个操做。当您调用任何异步EF方法的时候,始终使用“await”关键字。

  • 若是你想使用异步的性能优点,请确保你使用的任何包(例如分页),他们调用的Entity Framework 方法,使用async发送到数据库中。

相关文章
相关标签/搜索