基於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發送到數據庫中。

相關文章
相關標籤/搜索