Upsert
這是一個非常典型的需求:
傳入一筆資料,用 Primary Key(或唯一欄位)去比對資料庫。
- 若不存在 → 新增
- 若存在 → 更新欄位
也就是所謂的 Upsert(Update + Insert)
會員/客戶資料整併
會員/客戶資料整併(CRM / User Profile)時,第三方登入回傳一組 ExternalId(Google/Facebook/LINE),系統收到最新暱稱、頭像、Email
- 沒有這個 ExternalId → 新增會員
- 已存在 → 更新最新資料(頭像、名稱、聯絡資訊)

1 | existing.PropA = record.PropA; |
問題是當 Entity 有三十個欄位時,這樣的程式碼不僅冗長、容易漏改,也很難維護。這種情況下,我們就該讓 EF Core 幫你「批次更新」屬性!

EF Core 的解法:CurrentValues.SetValues()
EF Core 提供了一個非常實用的 API
1 | _dbContext.Entry(existing).CurrentValues.SetValues(record); |
這行的意思是把 record 物件中的所有屬性值,直接複製到 existing 這個追蹤中的實體上

Entry 的型別是 EntityEntry
1 | var entry = _dbContext.Entry(existing); |
其中 existing 必須是被追蹤中的實體(通常你用 FirstOrDefaultAsync 查回來、而且沒有 AsNoTracking(),它就會被追蹤)

可以從 entry 讀到很多重要資訊
entry.State:Added/Unchanged/Modified/Deleted/Detachedentry.OriginalValues:第一次從資料庫讀回來時的快照entry.CurrentValues:目前記錄在 Change Tracker 裡的屬性值(通常等於你物件上的值)entry.Properties:每個屬性的追蹤項目,可控制 IsModified 等旗標entry.References/entry.Collections:導覽屬性與集合
也就是說,Entry 是 Change Tracker 對單一實體的「狀態面板」

把 source 物件裡「名稱相同」的屬性值,複製到 entry 目前追蹤的實體上,它只比對同名屬性:source 的屬性名稱必須和目標 Entity 的屬性名稱相同才會被複製(型別需可指派 / 相容),source 可以是同型別的實體、DTO、甚至匿名型別,但只處理純量屬性,不會遞迴處理導覽屬性(關聯實體)或集合。這避免了不小心把關聯整棵樹都覆寫,對 主鍵 / 唯讀 / 計算欄位的複製會受限。像 Identity 主鍵是不允許修改的,若硬改會拋例外

他的修改偵測,就是把值塞進 CurrentValues 後,EF Core 會比較 OriginalValues 與 CurrentValues,把不同的屬性標記為 IsModified = true,SaveChanges() 時,EF 就會針對「被標記修改」的欄位產生 UPDATE。
SetValues = 用名字對齊、批量貼上屬性值 → 自動幫你標記「哪些欄位改了」


🌊 實際範例:Upsert 方法
以下範例展示如何以「唯一名稱」進行新增或更新
1 | public async Task UpsertRD() |

🌊 為什麼要設定 addallen.RdId = allen.RdId;

假設 Id 欄位是資料庫自動遞增的主鍵(Identity),如果直接 SetValues(),EF Core 會嘗試將 record 裡的 Id 值也覆蓋掉,這樣會讓追蹤中的實體被誤認為「新的物件」,而導致錯誤 The property 'RdId' is part of a key and cannot be modified.
因此我們要手動設定
1 | addallen.RdId = allen.RdId; |
讓兩邊的主鍵一致,EF Core 才能判定這筆資料是要更新 (Update),而不是新增 (Insert)
1 |
|










