Entity Framework筆記(一)_EF歷史與簡介_Code First_Migration指令操作與EF API使用


撇除微軟平台ORM工具 EF
我們先來了解ORM Tools帶來的效益

在各種程式語言(Java,Php,C#...)中都有類似工具與框架
用來提供在compile階段就能有type safe的保證機制
協助開發者透過物件導向方式在對DB進行相關存取與操作


.NET ORM 框架工具


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




 EF是由微軟推出的一種ORM框架自5.0過後就變為open source了
主張透過類似操作Object方式來控管DB


.NET Framework 發展時間軸



EF的發展時間軸

早期的第一版EntityFramework
從visual studio 2008開始(.net 3.5 sp1)
只有提供 Design-First模式


直到visual studio推出2010版本相應.net 4.0一併推出 Entity Framework第四版
多出所謂的Code First 以及 POCO(Plain Old Class Object)等觀念
POCO 有點跟DTO概念類似
就是藉由傳統的Class來對應DB結構做設計,簡化EF開發過程。
EF會自動處裡相應轉換


直到.net4.0之後的發展(vs2012開始 .net 4.5的釋出)
從EF5開始的版本都能直接透過Nuget來額外安裝
別於以往相依在.net framework 的package當中





主要分成如下三種開發型態
(一)GUI拖曳方式的
  • Database-First:從既有的資料庫藉由GUI精靈產生EDMX檔案(xml格式的檔案),
  • Model-First:產生Entities , relationships 跟 繼承階層

(二)OOP方式的
  • Code-First:優先開始撰寫Class
各自使用時機



.NET 3.5之前的開發上 開發者習慣透過ADO.NET進行資料庫程式開發
Entity Framework是一個open source 的 ORM(Object-Relational Mapper)
第一版Entity Framework 於 2008釋出

EF VersionRelease Year.NET Framework
EF 6.2
EF 6.1
EF 6
2017
2015
2013

.NET 4.0 & .NET 4.5, VS 2012
EF 52012.NET 4.0, VS 2012
EF 4.32011.NET 4.0, VS 2012
EF 4.02010.NET 4.0, VS 2010
EF 1.0 (or 3.5)2008.NET 3.5 SP1, VS 2008


讓.NET程式開發人員可以透過Object去存取Database
讓開發者可以用更少的程式份量去撰寫Data存取的程式

在資料存取透過Entity Data Model常見如下兩種type
Entity Classes
Derived context based on the database


Entity Framework優點
在程式與法中透過LINQ來對Object做query
對於CRUD(Create/Read/Update/Delete)操作簡易化處理採用物件導向式操作
程式碼自動生成
節省開發時間
可視化設計 Data Models 和database 相應mapping
讓型別可在編譯階段就先行有查檢確保Type Safety

Entity Framework缺點
效能會較ado.net遜色一些甚至有時會較差
若資料量較大或具有較複雜關聯情況不適用


Entity Framework運作方式
Entity Framework設計出來主要目的
在於給開發者能夠將data轉為entity形式來做處理
讓開發者能著重於應用程式邏輯而非資料庫中資料表和相應更動的維護
















本文將分享有關 EF 的 Code First開發模式
EF 建議支援開發環境必須要是vs2015 以上版本(vs2010,2012,2013舊版可能還要有額外配置)
並且SQL Server 2012之後版本(很重要)


Code First主要就是藉由Class描述來達到DB資料庫中各項相關操作
主要用於去克服和解決以往龐大系統上線後突然有資料庫中某些結構要改變
牽一髮動全身不好異動或是要改的地方太多容易遺漏等諸多程式維護的問題
(想到以前要擴充DB欄位時候用NodePad++ 全文搜尋方式先把
將近200~300頁.asp檔案SQL語句有用到的Table名稱模糊搜尋出來,再一一逐筆去編輯更新程式檔案這種古老時期的維護方式,就覺得十分恐怖啊....)

EF Code First主要有十分方便的DB Migration(移轉)機制使我們不必擔心後期DB結構異動問題。
當然任何DB操作都必須透過code來實踐(DB創建、欄位擴充、改欄位名稱、格式...)






EF Code First筆記大綱

  • Create the Application 
  • Create the Model
  • Create a Context
  • Where’s My Data?
  • EF Custom Conventions
  • Reading & Writing Data
  • Dealing with Model Changes
  • Data Annotations
  • Fluent API


Create the Application

首先創建好一空方案
對方案右鍵添加新的Class Library(based on .net framework)新增DAL.cs


Create the Model

新建 Category.cs 和 Product.cs 兩Model class





商品跟類別的關聯
一種類別會對應一或多項商品
一項商品只會對應一種類別(記得要用public修飾)


Create a Context
這裡我們目前只是針對Table每一筆Record Model (相當於Table)在進行透過
code 描述
再創建一DatabaseContext.cs的 Class 也就是我們的資料庫

這裡我們針對DAL專案去Nuget下載EF  目前來至6.4版
(也可在Package Console下指令Install-Package EntityFramework)


我們讓自行創建的DatabaseContext類別去繼承EF的DbContext
DbContext在EF中扮演著很重要的角色
主要是DB跟系統自行建立的Entity Class中溝通的橋梁
更減化我們跟DB互動的程式量
預設是沒內建在.net framework中需要額外自Nuget引入EntityFramework.dll


DbSet表示的是一個特定Entity集合(也就是DB中Table)



Where’s My Data?


至config設置連線字串

1
2
<add name="DefaultConnection" providerName="System.Data.SqlClient" 
			 connectionString="data source={DB ServerName};initial catalog={Db Name};integrated security=True;Persist Security Info=True;User ID=xxx;Password=xxxx;multipleactiveresultsets=True;application name=EntityFramework" />








再來是DB Migration操作流程

Migration主要是在發生異動時候就需要執行的動作(會記錄每次異動的差異處並更新DB)
分為自動、手動

這裡示範的是手動
預設在Configuration.cs的constructor中,自動migration是被turn false的。
如要自動就要將其turn on。

手動Migration

Step1.啟用Migration
在套件管理員主控台中執行 
enable-migrations 

此命令會將 Migrations 資料夾新增至專案。
 起初由於我們DB Server並未包含此DB
因此預設會產生一組態的cs檔案而已



Step2.添加Migration(遷移、移轉)快照
在套件管理員主控台中執行 
add-migration  {自行命名migration名稱}

(詳細用法可參照:get-help add-migration )

建立第一個 Migration,執行scaffold 遷移,以將這些變更套用至資料庫。
在進行Migration前,先新增一個Model快照檔,然後再執行Migration。



預設產生一個 "日期_{自行命名migration名稱}.cs" 檔案
202104150522044_initial.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
namespace DAL.Migrations
{
    using System;
    using System.Data.Entity.Migrations;
    
    public partial class initial : DbMigration
    {
        public override void Up()
        {
            CreateTable(
                "dbo.Categories",
                c => new
                    {
                        CategoryId = c.Int(nullable: false, identity: true),
                        Name = c.String(),
                    })
                .PrimaryKey(t => t.CategoryId);
            
            CreateTable(
                "dbo.Products",
                c => new
                    {
                        ProductId = c.Int(nullable: false, identity: true),
                        Name = c.String(),
                        Description = c.String(),
                        UniPrice = c.Decimal(nullable: false, precision: 18, scale: 2),
                        CategoryId = c.Int(nullable: false),
                    })
                .PrimaryKey(t => t.ProductId)
                .ForeignKey("dbo.Categories", t => t.CategoryId, cascadeDelete: true)
                .Index(t => t.CategoryId);
            
        }
        
        public override void Down()
        {
            DropForeignKey("dbo.Products", "CategoryId", "dbo.Categories");
            DropIndex("dbo.Products", new[] { "CategoryId" });
            DropTable("dbo.Products");
            DropTable("dbo.Categories");
        }
    }
}

該支程式代表一組DB快照包含有Up() 跟 Down()兩方法
Up() 主要是Create Table 跟相關PK、FK、Index、諸多關聯
Down()則是Drop Table 跟相關PK、FK、Index



在套件管理員主控台中執行 
update-database -verbose

(詳細用法可參照:get-help update-database )
將新的遷移套用至資料庫
update-database 命令會執行 Up 方法來建立資料庫
可以指定Migration快照檔為參數,也可不指定任何參數。 
不指定任何參數時,就使用最後一次建置的Migration快照檔來更新資料庫。如果指定了MIgration快照檔,就會把資料庫還原到指定快照檔的狀態。

後面多加–verbose
可以去print出 SQL satements執行異動的詳情


於DB Server中就能看到我們DB已經被EF創建出來了



EF Custom Conventions

藉由EF Code First  產出來的Table Schema 欄位格式會有預設的欄位資料型態約定
EF當中的預設convention
Property叫Id或{Class名}Id且為int則會被當作PK
Property為另一{Class Type名}預設就會被視為Navigation Proerty
Property為另一{Class名}Id則會被當作FK

其他還有
string 型態property則會對應為nvarchar(max)
decimal型態的property則對應為decimal(18,2)
.....
以下是自此摘錄的總表

Data TypeMapped to
stringnvarchar(max)
decimaldecimal(18, 2), not null
decimal?decimal(18, 2), null
doublefloat, not null
double?float, null
intint, not null
int?int, null
boolbit, not null
bool?bit, null
DateTimedatetime, not null
DateTime ?datetime, null
byte[]varbinary(max)


通常在ClassA中又定義了資料型態為ClassB的Property就會稱之為Navigation Proerty(導覽屬性)
ClassA當中會跟ClassB產生可能是1對1又或者1對多的映射關聯



至於要怎麼區別 "1對1"  還是  "1對多"
以我們目前Category跟Product之間的關係來看

在Product.cs 中包含著一Navigation Proerty型態為Category 因此為
1個Product 對1種Category
也就是 "1對1"

而反過來看
在Category.cs中包含著一Navigation Proerty型態為ICollection<Product>
代表一種Category對多個Product
也就是"1對多"



A navigation property is an optional property on an entity type that allows for navigation from one end of an association to the other end. Unlike other properties, navigation properties do not carry data.

何謂導覽(導航)屬性?
跟FK又有捨麼關係? 
Navigation Proerty是否等同於FK?
微軟官方文件中有針對關聯性、導覽屬性和外鍵做介紹

在探討導覽屬性之前先回顧一下資料表的關聯是如定義
關聯式資料庫當中關鍵設計就在於各類資料表之間如何關聯,主要藉由FK(外部索引鍵)來定義,FK可用來強制建立兩資料表之間的連結關係。
一般Table之間關聯可分為三種:
一對一(One-to-One):
某一資料表單筆資料列跟另一資料表的資料列1筆對應1筆,反之亦然。
較常見的就是某張資料表的PK是另一張資料表的Unique FK

一對多(One-to-Many):
某一資料表單筆資料列跟另一資料表的資料列1筆對應1或多筆
較常見的就是某張資料表的PK是另一張資料表的 FK

多對多(Many-to-Many):
某一資料表多筆資料列跟另一資料表的資料列對應多筆
比方課程跟學生(學生可以修多項課程,一門課程也可開放給多位學生修課)

導覽屬性代表的是定義於Table Class之間各自可相互訪問存取的屬性(你有我,我也有你)

外鍵則是EF經過分析兩Table Class各自導覽屬性後獲知彼此是1對1或1對多
誰依賴誰(從哪邊映射到哪)關係,最終才抉擇FK該設置給哪邊Table的哪個Column (EF Property)

以目前商品跟種類就被EF認定之間關係為一對多(One-to-Many)
一種類對應多項商品,再加上上述有提到的默認EF Convention,Property為另一{Class名}Id則會被當作FK,因此設定Product中的CategoryId為FK。

此外Navigation Property還細分為三種載入模式

1.延遲式載入(Lazy loading):
默認的模式,有對導覽屬性進行存取才去DB撈資料

2.積極式載入(Eager Loading):
資料載入時會連帶將有關聯的資料一併載入

3.明確式載入(Explicit loading):
嚴謹控管資料撈取的時機,只在程式有要求執行時才會去DB撈資料












Data Annotation (資料/數據註解屬性)

[Table(tbname)]
public class xxxx
{

}
在類名稱上方設置註釋[Table(tbname)],
將類對應到名稱為tbname的資料表,在慣例設計 的原則下,
類名稱默認會映射到資料表名稱。
通過這一組資料註解可以調整映射的默認行為

public class xxx
{
      [Key]
      public int SId {get;set;}
      [Column("stu_name")]
      public string StudentName {get;set;}
      [Column("stu_fee" , TypeName="Money")]
      public decimal Tuition {get;set;}
}

通過標識[key]避開慣例原則強制指定主鍵字段。
由於每一個屬性會直接映射到數據表中的同名字段,
屬性Name的註解[Column('cname")強制將這個屬性映射到cname, 
甚至可以通過數據註解指定映射的欄位資料型態,
例如[Column ("cname", TypeName = "type'], 
如此一來,屬性映射的字段將會是type類型。

以範例來講 SId
雖然不符合
Property叫Id或{Class名}Id且為int則會被當作PK
但是藉由在欄位上多加[Key]註解
已經變成PK
在StudentName屬性中我們則可透過[Column("stu_name")]的註解
將其DB欄位改成stu_name的簡寫
而學費屬性Tuition部分除了名稱外更進階透過
TypeName設定為Money使其不會變成decimal

public class UserAccount
{
        [Required]
        public string account {get;set;};
        [Required]
        [MaxLength(50) , MinLength(10)]
        public string Password {get;set;};
}

如果有欄位是屬於必填寫的不可為null則可標識
[Required]
比方註冊登入所需的帳號與密碼
另外如有資料長度上下限則可各自標識
[MaxLength(長度數值)]
[MinLength(長度數值)]
或同時設定
[MaxLength(長度數值) , MinLength(長度數值)]
如果有想要返回警示錯誤訊息則可
[MaxLength(長度數值,ErrorMessage="長度不可超出50") , 
MinLength(長度數值,ErrorMessage="長度不可小於10")]


有些時候我們的Model 中建立的屬性不一定都對應到DB中某張table
所有欄位可能有其他用途時候
此時想要避開被認定為DB Table中欄位轉換處裡
則可標識為[NotMapped]

[Index]




Reading & Writing Data

















Ref:
https://docs.microsoft.com/zh-tw/ef/ef6/fundamentals/working-with-dbcontext
https://docs.microsoft.com/zh-tw/ef/ef6/what-is-new/visual-studio
https://docs.microsoft.com/zh-tw/ef/ef6/what-is-new/past-releases#ef-613
https://docs.microsoft.com/zh-tw/ef/ef6/get-started
https://www.huanlintalk.com/2012/12/entity-framework-dbcontext-lifetime-in.html
https://www.entityframeworktutorial.net/entityframework6/dbset.aspx

Entity Framework Conventions

[Entity Framework][Code First]One-to-One Relationships

[Entity Framework][Code First]One-to-many relationship

[Entity Framework][Code First]Many-to-Many Relationships

Entity Framework应用:导航属性

Entity framework + Linq 介紹

EF Code First 导航属性 与外键








留言

這個網誌中的熱門文章

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

經得起原始碼資安弱點掃描的程式設計習慣培養(三)_7.Cross Site Scripting(XSS)_Stored XSS_Reflected XSS All Clients

(2021年度)駕訓學科筆試準備題庫歸納分析_法規是非題