基於Asp.Net Core Mvc和EntityFramework Core 的實戰入門教程系列-2


來個目錄吧:
第一章-入門
第二章- Entity Framework Core Nuget包管理
第三章-創建、修改、刪除、查詢
第四章-排序、過濾、分頁、分組
第五章-遷移,EF Core 的codefirst使用
暫時就這么多。后面陸續更新吧

Entity Framework Core Nuget包管理

如果你創建項目的時候啟用了個人身份驗證的話,項目中就已經包含了EFCore的支持。
如果你是單純的空項目想將EFCore添加到你的項目中話,你需要安裝一下的Nuget程序包:

vs2017可以直接進行編輯項目的.csproj文件,安裝所需軟件包。
···





···
(您可以編輯的.csproj文件右擊解決方案資源管理器中的項目名稱並選擇編輯 ContosoUniversity.csproj)。

我又來了,我親測了下.NETCORE1.1目前不支持Microsoft.EntityFrameworkCore.Tools.DotNet這樣玩,這里先略過

創建數據模型

創建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 ``

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM