EntityFramework Core筆記(1)_CodeFirst配置_搭配MVC應用的開發_新增、查詢

 
在之前該篇有大致上介紹到配置方式DB-First

EF 是一套對於.Net的ORM框架,而EF Core則是針對.Net5(.Net Core)的延伸。
ORM(Object-Relational Mapping)可以理解為關聯式資料庫和物件之間根據某種規則而建立的一套映射關係,取代底層資料庫操作而透過物件的coding來操作,主要都是透過物件的形式在做DB存取更新刪除的相關操作。
其他ORM框架像是Dapper , NHibernate都是其中一種。


新建好.net core mvc專案




EF Core的安裝
Microsoft.EntityFrameworkCore.sqlserver







一般在專案架構中可能常見的三層或多層架構會額外存在一個Model層又被稱為「實體模型」。
實體模型和之前提到的檢視模型有比較大區隔,實體模型專注於資料表結構關聯在各層之間的傳遞交換,這些資料最終透過DB訪問層傳到DB中。
檢視模型則是在檢視和控制器之間進行資料傳遞交換,檢視模營無法脫離UI層。
因此也可以建議一個後綴用ViewModel一個用DataModel來做區分。


Step1.準備好資料實體模型(各自都對應一張table)於Data Folder下



這裡資料字典如下






StudentDataModel

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Net5EFCore_App1.Data
{
    public class StudentDataModel
    {
        public Guid Id { get; set; }
        public string Name { get; set; }
        public int Age { get; set; }
        public bool Sex { get; set; }

        public ICollection<CourseDataModel> Courses { get; set; }
    }
}

在StudentDataModel中
增加一個導航屬性ICollection<CourseDataModel>
代表一個學生可以選一或多個課程
預設只要在StudentDataModel加上該導航屬性
就會自動幫我們在Course生產出FK 並自動命名為StudentDataModelId
但是因為我們想要命名為StudentId
因此才需要在CourseDataModel class中額外定義這三行
public Guid StudentId { get; set; }
[ForeignKey("StudentId")]
public StudentDataModel Student { get; set; }

若是要針對EF Core的Data Model需要做資料註解
需多引入using System.ComponentModel.DataAnnotations.Schema;




CourseDataModel

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Threading.Tasks;

namespace Net5EFCore_App1.Data
{
    public class CourseDataModel
    {
        public Guid Id { get; set; }
        public string Semester { get; set; }
        public string Title { get; set; }

        public Guid StudentId { get; set; }
        [ForeignKey("StudentId")]
        public StudentDataModel Student { get; set; }
    }
}


Step2.建立DbContext衍生類

MyTestDbContext.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Net5EFCore_App1.Data
{
    public class MyTestDbContext : DbContext
    {
        public MyTestDbContext(DbContextOptions<MyTestDbContext> options) : base(options)
        {

        }

        public DbSet<StudentDataModel> Students { get; set; }
        public DbSet<CourseDataModel> Courses { get; set; }
    }
}



Step3.至appsettings.json配置DB connection string

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft": "Warning",
      "Microsoft.Hosting.Lifetime": "Information"
    }
  },
  "AllowedHosts": "*",
  "ConnectionStrings": {
    "MyTestDbConn" : "Server=.;Database=AspNetEFCoreDb;uid=sa;pwd=rootroot"
  }
}

Step4.至Startup.cs的ConfigureServices進行DB服務的依賴注入

Startup.cs多引入命名空間
using Microsoft.EntityFrameworkCore;
using Net5EFCore_App1.Data;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Net5EFCore_App1.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Net5EFCore_App1
{
    public class Startup
    {
        public Startup(IConfiguration configuration)
        {
            Configuration = configuration;
        }

        public IConfiguration Configuration { get; }

        // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddDbContext<MyTestDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MyTestDbConn")));
            services.AddControllersWithViews();
        }

        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler("/Home/Error");
            }
            app.UseStaticFiles();

            app.UseRouting();

            app.UseAuthorization();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    }
}


Step5.至主程式Program.cs中的Main()去實踐第一次(只執行一次)的DB上下文建立
把原先的程式碼CreateHostBuilder(args).Build().Run();刪掉or註解掉
需引入命名空間Microsoft.Extensions.DependencyInjection
using Microsoft.Extensions.DependencyInjection;

改為如下

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Net5EFCore_App1.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Net5EFCore_App1
{
    public class Program
    {
        public static void Main(string[] args)
        {
            //CreateHostBuilder(args).Build().Run();
            var host = CreateHostBuilder(args).Build();
            using (var scope = host.Services.CreateScope())
            {
                var services = scope.ServiceProvider;
                try
                {
                    var dbContext = services.GetRequiredService<MyTestDbContext>();
                    dbContext.Database.EnsureCreated();
                }
                catch (Exception ex)
                {
                    var logger = services.GetRequiredService<ILogger<Program>>();
                    logger.LogError(ex, "Create DB structure error. ");
                }
            }
            host.Run();
        }

        public static IHostBuilder CreateHostBuilder(string[] args) =>
            Host.CreateDefaultBuilder(args)
                .ConfigureWebHostDefaults(webBuilder =>
                {
                    webBuilder.UseStartup<Startup>();
                });
    }
}


我們希望讓Web應用於一開始啟動時
就會在Startup class中註冊的DB上下文服務能獲得
我們自行建立的衍生DbContext物件 "MyTestDbContext"



這裡藉由dbContext.Database.EnsureCreated();此方法來創建DB
EnsureCreated() 回傳Boolean 
true   代表新的資料庫結構已完成建立。
false  代表資料庫結構已經存在不需重新建立。

這裡在啟動以前先觀察目前SSMS是還不存在AspNetEFCoreDb這個資料庫的


當運行之後就會自動生成


之後新增ViewModel於Model folder之下
若是要針對View的ViewModel需要做資料註解
需多引入using System.ComponentModel.DataAnnotations;

StudentViewModel.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace Net5EFCore_App1.Models
{
    public class StudentViewModel
    {
        public Guid Id { get; set; }
        
        [Display(Name="姓名")]
        [Required(ErrorMessage ="{0}不可為空")]
        public string Name { get; set; }

        [Display(Name="年齡")]
        [Required(ErrorMessage ="{0}不可為空")]
        [Range(18,60,ErrorMessage ="{0}的範圍介於{1}~{2}")]
        public int Age { get; set; }

        [Display(Name="性別")]
        [Required(ErrorMessage ="{0}不可為空")]
        public bool Sex { get; set; }
    }
}


CourseViewModel.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace Net5EFCore_App1.Models
{
    public class CourseViewModel
    {
        public Guid Id { get; set; }

        [Display(Name="學期")]
        [Required(ErrorMessage ="{0}不可為空")]
        public string Semester { get; set; }

        [Display(Name = "課程名稱")]
        [Required(ErrorMessage = "{0}不可為空")]
        public string Title { get; set; }

        public Guid StudentId { get; set; }
    }
}




增加學生資訊

這裡要來準備建立學生的控制器跟相應檢視
action 添加部分 分為顯示用跟提交表單到後台資料庫存取兩部分


StudentController.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
using Microsoft.AspNetCore.Mvc;
using Net5EFCore_App1.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Net5EFCore_App1.Controllers
{
    public class StudentController : Controller
    {
        private readonly MyTestDbContext _dbContext;
        /// <summary>
        /// 透過建構子注入DbContext
        /// </summary>
        /// <param name="dbContext"></param>
        public StudentController(MyTestDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        /// <summary>
        /// 增加學生資訊的頁面-顯示
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public IActionResult Add()
        {
            return View();
        }

        /// <summary>
        /// 增加學生資訊的頁面-提交
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [HttpPost]
        public IActionResult Add(StudentDataModel model)
        {
            if (ModelState.IsValid)
            {
                StudentDataModel studentData = new StudentDataModel();
                studentData.Id = Guid.NewGuid();
                studentData.Name = model.Name;
                studentData.Age = model.Age;
                studentData.Sex = model.Sex;

                _dbContext.Add(studentData);
                _dbContext.SaveChanges();
                return RedirectToAction("Show");
            }
            return View(model);
        }
    }
}


Add.cshtml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@{
    ViewData["Title"] = "增加學生資訊";
}
@model StudentViewModel

<h3>@ViewData["Title"]</h3>

<form method="post" asp-controller="Student" asp-action="Add">
    <div>
        <div class="form-group col-4">
            <label asp-for="Name"></label>
            <input asp-for="Name" class="form-control" />
            <span asp-validation-for="Name"></span>
        </div>
        <div class="form-group col-4">
            <label asp-for="Age"></label>
            <input asp-for="Age" class="form-control" />
            <span asp-validation-for="Age"></span>
        </div>
        <div class="form-group col-4">
            <label asp-for="Sex"></label>
            <input type="radio" asp-for="Sex" value="true" checked="checked" /><input type="radio" asp-for="Sex" value="false" /><span asp-validation-for="Sex"></span>
        </div>
        <div class="form-group col-4">
            <input type="submit" value="保存" class="btn btn-primary" />
        </div>
    </div>
</form>
@section scripts{
    @{
        await Html.RenderPartialAsync("_ValidationScriptsPartial");
    }
}

執行後
顯示新增學生資訊的表單畫面



提交後確認存入DB




顯示學生資料

目前資料已經存入DB了而顯示學生資訊的頁面Show
這裡額外擴增一Action Show來

StudentController.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Net5EFCore_App1.Data;
using Net5EFCore_App1.Models;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace Net5EFCore_App1.Controllers
{
    public class StudentController : Controller
    {
        private readonly MyTestDbContext _dbContext;
        /// <summary>
        /// 透過建構子注入DbContext
        /// </summary>
        /// <param name="dbContext"></param>
        public StudentController(MyTestDbContext dbContext)
        {
            _dbContext = dbContext;
        }

        /// <summary>
        /// 增加學生資訊的頁面-顯示
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public IActionResult Add()
        {
            return View();
        }

        /// <summary>
        /// 增加學生資訊的頁面-提交
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [HttpPost]
        public IActionResult Add(StudentDataModel model)
        {
            if (ModelState.IsValid)
            {
                StudentDataModel studentData = new StudentDataModel();
                studentData.Id = Guid.NewGuid();
                studentData.Name = model.Name;
                studentData.Age = model.Age;
                studentData.Sex = model.Sex;

                _dbContext.Add(studentData);
                _dbContext.SaveChanges();
                return RedirectToAction("Show");
            }
            return View(model);
        }

        /// <summary>
        /// 顯示查詢出來的學生資訊
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public async Task<IActionResult> Show()
        {
            var students = await _dbContext.Students.ToListAsync();
            var vm_Student = new List<StudentViewModel>();
            foreach (var stu in students)
            {
                vm_Student.Add(new StudentViewModel() { Id = stu.Id, Name = stu.Name, Age = stu.Age, Sex = stu.Sex });
            }
            return View(vm_Student);
        }

    }
}



已注入進來Controller的DbContext衍生的Class可透過Students物件來得到Table中所有資料群
EF Core預設提供了非同步的ToListAsync()跟同步的ToList()兩種獲取資料的方法
若要使用非同步的需引入using Microsoft.EntityFrameworkCore;

Show.cshtml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@{
    ViewData["Title"] = "顯示學生資訊";
}

<h3>@ViewData["Title"]</h3>

@model IEnumerable<StudentViewModel>

<table class="table table-bordered">
    <tr>
        <td>@Html.DisplayNameFor(m => m.Id)</td>
        <td>@Html.DisplayNameFor(m => m.Name)</td>
        <td>@Html.DisplayNameFor(m => m.Age)</td>
        <td>@Html.DisplayNameFor(m => m.Sex)</td>
    </tr>
    @foreach (StudentViewModel stu in Model)
    {
        <tr>
            <td>@Html.DisplayFor(m => stu.Id)</td>
            <td>@Html.DisplayFor(m => stu.Name)</td>
            <td>@Html.DisplayFor(m => stu.Age)</td>
            <td>@Html.DisplayFor(m => stu.Sex)</td>
        </tr>
    }
</table>


執行效果
預設顯示性別的欄位部分不夠直覺 勾勾打起來跟沒有打各自分別代表男還女會不知道因此可以再稍微調整

StudentViewModel.cs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;

namespace Net5EFCore_App1.Models
{
    public class StudentViewModel
    {
        public Guid Id { get; set; }
        
        [Display(Name="姓名")]
        [Required(ErrorMessage ="{0}不可為空")]
        public string Name { get; set; }

        [Display(Name="年齡")]
        [Required(ErrorMessage ="{0}不可為空")]
        [Range(18,60,ErrorMessage ="{0}的範圍介於{1}~{2}")]
        public int Age { get; set; }

        [Display(Name="性別")]
        [Required(ErrorMessage ="{0}不可為空")]
        public bool Sex { get; set; }

        [Display(Name = "性別")]
        public string StrSex { get; set; }
    }
}


在Action Show中可以去擴充該欄位承接字串結果

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
        /// <summary>
        /// 顯示查詢出來的學生資訊
        /// </summary>
        /// <returns></returns>
        [HttpGet]
        public async Task<IActionResult> Show()
        {
            var students = await _dbContext.Students.ToListAsync();
            var vm_Student = new List<StudentViewModel>();
            foreach (var stu in students)
            {
                vm_Student.Add(new StudentViewModel() { Id = stu.Id, Name = stu.Name, Age = stu.Age, Sex = stu.Sex, StrSex = stu.Sex ? "男" : "女" });
            }
            return View(vm_Student);
        }



Show.cshtml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@{
    ViewData["Title"] = "顯示學生資訊";
}

<h3>@ViewData["Title"]</h3>

@model IEnumerable<StudentViewModel>

<table class="table table-bordered">
    <tr>
        <td>@Html.DisplayNameFor(m => m.Id)</td>
        <td>@Html.DisplayNameFor(m => m.Name)</td>
        <td>@Html.DisplayNameFor(m => m.Age)</td>
        <td>@Html.DisplayNameFor(m => m.Sex)</td>
    </tr>
    @foreach (StudentViewModel stu in Model)
    {
        <tr>
            <td>@Html.DisplayFor(m => stu.Id)</td>
            <td>@Html.DisplayFor(m => stu.Name)</td>
            <td>@Html.DisplayFor(m => stu.Age)</td>
            @*<td>@Html.DisplayFor(m => stu.Sex)</td>*@
            @*<td>@(stu.Sex ? "男" : "女")</td>*@
            <td>@Html.DisplayFor(m=>stu.StrSex)</td>
        </tr>
    }
</table>



運行起來的效果就會較直覺





























留言

這個網誌中的熱門文章

何謂淨重(Net Weight)、皮重(Tare Weight)與毛重(Gross Weight)

Architecture(架構) 和 Framework(框架) 有何不同?_軟體設計前的事前規劃的藍圖概念

經得起原始碼資安弱點掃描的程式設計習慣培養(五)_Missing HSTS Header