---
# System prepended metadata

title: Entity Framework
tags: [Learning]

---

# Entity Framework
###### tags: `Learning`

## 重要須知
1. 在 Query 的時候，先下 Where、Select... 再去進行 Sum、Count、ToList...等的動作。IQueryable 在下 Where、Select... 時，還不會去執行 Query；Sum、Count、ToList 會去執行 Query。所以下好條件再去執行，才不會影響效能。
2. EF 的 Model 本身就有自己偷偷紀錄哪個欄位被改，所以只要取出資料並修改(ex. context.tableA.FirstOrDefault().ColumnA = "666")，改好後 SaveChange，就會將 ColumnA = "666" 改到 DB 中！

---

## ★★★接SP的回傳結果 ( EF真的屌 )
SP長這樣的
![](https://i.imgur.com/fTnrj85.png)

EF產出的SP的Function長這樣
![](https://i.imgur.com/zZTfhWP.png)

EF產出的SP的Function中，有一個output參數，但Function沒定義他，於是我自己定義了傳進去。
![](https://i.imgur.com/oKoJVvk.png)

執行完SP之後，EF將值塞入了我剛定義的參數中
![](https://i.imgur.com/ioXnfo8.png)

---

## Unit Of Work & Repository

Unit of work = DB
Repository = Table

Unit of work 裡面有 Context 物件
Repository 建構子需傳入 Unit of work 物件

為了不要使用一個 table 就開一個連線 (?)，但是其實用 using (Context context = new Context()) 也可以解啦

使用 Repository 來跟資料庫做連結
像是這有三個 Service 其實可以把 Repository 寫成泛型，一個檔案就能解決了
![](https://i.imgur.com/eqWMf3U.png)

---

## 不想用 Unit of work And Repository

```
 using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted }))
using (YourContext context = new YourContext())
{
    // Do something...
    // 如果有更改資料，記得 SaveChange & Complete
    // 如果只有 Call SP，只知道不用 SaveChange，不知道要不要 scope.Complete？
    // context.SaveChanges();
    // scope.Complete();
}
```

---

## EF DB Model 小技巧
每次 Entity Framework 都會重建 DB Model，每次加上的 Attribute 會被洗掉，其實你可以自己建立一個 MetaData，定義好 Attribute，透過 MetaData 取資料就可以不用怕每次都被洗掉。

---

## 自訂驗證 Valid
特別注意自訂後端驗證，是<span style="color:red">前端驗證完之後，Post到後端進行 ModelBinding 後才會觸發</span>，所以你的 Action 必須要寫好對應要接值的參數 Model 型態，<span style="color:red">程式有需要 Binding 你剛剛自訂驗證的 Model，才會觸發自訂驗證的 Function 喔 !</span>

Model 必須繼承 IValidatableObject
![](https://i.imgur.com/KA6FwgK.png)
Function ↓
![](https://i.imgur.com/nDdrcDQ.png)
上圖的驗證是針對某個欄位的驗證，如果想要錯誤訊息寫在 ValidationSummary 的話，就不用特別傳第二個欄位的 string [] 參數(key)
※ 第二個欄位的 string [] 參數 (key) 是為了匹配 ValidationMessageFor

```
@Html.ValidationMessageFor(model => model.Document.FromOrg, "", new { @class = "text-danger" })
```

如下圖↓
![](https://i.imgur.com/k735ICz.png)
※ ValidationSummary 的第一個參數必須為 true (顯示模型層級的錯誤，就是非 Property 的驗證錯誤，簡單來說就是 沒有給 Key 的 Error 啦~)

```
@Html.ValidationSummary(true, "", new { @class = "text-danger" })
```

#### 20200417 我在驗證上遇到了一些問題：
> 我 return 的 ValidationResult 明明就沒有給 key，為什麼會沒顯示在顯示在模型層級錯誤的 Summary 中？
> 
> 我的 Model 長這樣
> ![](https://i.imgur.com/Ym0czZC.png)
> 自訂驗證寫在 tblDocument 的 Model 中

後來查發現是 ValidationResult 還是有給 Key，他給的 Key 是 "Doucument" ( 因為被 ViewModel 包起來的關係，可能微軟就覺得他不算是模型層級的錯誤，要解的話，應該是將自訂驗證寫在 ViewModel 中，我猜這樣 ValidationResult 就不會自動給 key 了)

※備註: 一般的 Attribute (Ex.Required)，都是在前端進行作業，會在 Html 標籤中給驗證相關的屬性，所以並不會進到後端，所以才要自訂驗證。

---

## Metadata & 自訂驗證方式
※用 EF 產出的 Model 不可加上 Attribute，到時候 DB 更新 Model 重抓就會遺失

※自訂後端驗證必須掛在原本的 Model 上

```
    [MetadataType(typeof(tblDocumentsMeta))]
    public partial class tblDocuments : IValidatableObject
    {
        internal sealed class tblDocumentsMeta 
        {
            [DisplayName("收文編號")]
            public string DocumentID { get; set; }

            [Required(ErrorMessage = "收文日期 為必填 !")]
            [DisplayName("收文日期")]
            [DisplayFormat(DataFormatString = "{0:yyyy/MM/dd}", ApplyFormatInEditMode = true)]
            public System.DateTime DocumentDate { get; set; }

            public bool IsReceived { get; set; }

            [Required(ErrorMessage = "主旨 為必填 !")]
            [DisplayName("主旨")]
            [StringLength(200, ErrorMessage = "最多只能填 200 個字 !")]
            public string Subject { get; set; }

            [DisplayName("內容")]
            public string Content { get; set; }

            [Required(ErrorMessage = "來文單位 為必填 !")]
            [DisplayName("來文單位")]
            public string FromOrg { get; set; }

            [Required(ErrorMessage = "來文字號 為必填 !")]
            [DisplayName("來文字號")]
            public string FromID { get; set; }

            [Required(ErrorMessage = "來文日期 為必填 !")]
            [DisplayName("來文日期")]
            [DisplayFormat(DataFormatString = "{0:yyyy/MM/dd}", ApplyFormatInEditMode = true)]
            public Nullable<System.DateTime> FromDate { get; set; }

            [Required(ErrorMessage = "類別 為必填 !")]
            [DisplayName("類別")]
            public Nullable<int> Class { get; set; }

            [Required(ErrorMessage = "等級 為必填 !")]
            [DisplayName("等級")]
            public string Level { get; set; }

            [Required(ErrorMessage = "歸屬單位 為必填 !")]
            [DisplayName("歸屬單位")]
            public string DepartmentTo { get; set; }

            [DisplayName("DepartmentFrom")]
            public string DepartmentFrom { get; set; }

            [DisplayName("承辦人")]
            public string CaseOfficer { get; set; }

            public bool UseStatus { get; set; }

         
        }

        #region Validate
        public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
        {
            var data = new HRContext().tblDocuments.ToList();

            // 檢查使用者填入的來文單位、來文日期、來文單號在資料庫裏面不得重複，否則顯示錯誤提示
            if (data.Any(x => x.FromOrg == FromOrg && x.FromDate == FromDate && x.FromID == FromID))
                yield return new ValidationResult($"來文單位: {FromOrg}、來文日期: {FromDate.Value.ToString("yyyy/MM/dd")}、來文單號: {FromID}，已存在", new string[] { "FromOrg" });
        }
        #endregion
    }

```

---

## 使用 AsNoTracking
使用 AsNoTracking 的話，會 always 讀取最新資料，不會從 DB 的 Cache 取得資料。

```
return _Context.Employees.AsNoTracking().ToList();
```           
            
---

## ProxyCreationEnabled
會建立一個動態的 Class，其中裡面還是包含實體的 Model 連結
```
_Context.Configuration.ProxyCreationEnabled = true;
```

---

## 將結果顯示在 "輸出" 視窗中

```
_Context.Database.Log = s => Debug.WriteLine(s);
```

![](https://i.imgur.com/JZflzpS.png)

![](https://i.imgur.com/3YbXhLk.png)

---

## TransactionScope 出現了 基礎提供者在 Open 失敗 !!

```
using (TransactionScope scope = new TransactionScope(TransactionScopeOption.RequiresNew, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadUncommitted }))
using (HRContext context = new HRContext())
{
    // Do context 相關的操作... ( EF 的連線 )
    
    // Do Service 相關的操作... ( unitOfwork 的連線 )
}
```
這時候就會是兩條連線 => 分散式交易

需要特別處理 ( 尚未知道怎麼處理 )

---