前言
前面我們已經練習過使用 Dapper,剛好前輩提起 EF 也是需要練習熟悉的,就做了一個 EF Core 的版本,基本上內容與先前 API 第二版本大致相同,這邊就只會列出差異部分與碰到的問題。
本文
一、首先我們會需要先安裝三個 EF 需要使用到的 Nuget 套件。
分別是:
Nuget名稱 | 安裝地點 |
---|---|
EntityFrameworkCore | Repository |
EntityFrameworkCore.SqlServer | Repository |
EntityFrameworkCore.Tools | Controller |
二、接下來需要輸入指令建立 EFCore 所使用的 DBContext。
完整指令如下:
Scaffold-DbContext 'Data Source=localhost;Database=Northwind;Trusted_Connection=True;' Microsoft.EntityFrameworkCore.SqlServer -OutputDir Conditions -Tables Users -context AccountContext -Project WebApiEFCoreRepository -force
這邊提一下指令由前到後介紹:
Scaffold-DbContext 'Data Source=localhost;Database=Northwind;Trusted_Connection=True;' Microsoft.EntityFrameworkCore.SqlServer
這段是我們與 DB 連線字串,然後指定所使用的 DB 是 SQL server。
-OutputDir Conditions
這邊是生成檔案放置地點。
-Tables Users
指定的 Table 名稱。
-context AccountContext
這是會生成的 DBContext 檔案名稱,這邊我取名為 AccountContext。
-Project WebApiEFCoreRepository -force
指定生成在 Repository 專案,最後的 force 其實第一次生成 DBContext 時不用加,但後續要再加 Table 的時候可能會用上;至於我為甚麼留著呢?那當然是因為 懶惰
三、建完之後步驟。
指令建出來的 context 檔案其中有一段 DB 連線的地方這邊可以先拿掉,之後我們會使用注入的方式拿取連線字串。
接下來建立 IAccountRepository
public interface IAccountRepository
{
/// <summary>
/// 取得單筆帳號資訊
/// </summary>
/// <param name="account"></param>
/// <returns></returns>
public AccountCondition GetAccount(string account);
/// <summary>
/// 取得帳號列表
/// </summary>
/// <returns></returns>
public IEnumerable<AccountCondition> GetAccountList();
/// <summary>
/// 新增帳號
/// </summary>
/// <param name="condition"></param>
/// <returns></returns>
public bool AddAccount(AccountCondition condition);
/// <summary>
/// 刪除帳號
/// </summary>
/// <param name="account"></param>
/// <returns></returns>
public bool RemoveAccount(string account);
/// <summary>
/// 更新帳號資訊
/// </summary>
/// <param name="condition"></param>
/// <returns></returns>
public bool UpdateAccount(AccountCondition condition);
/// <summary>
/// 忘記密碼
/// </summary>
/// <param name="condition"></param>
/// <returns></returns>
public bool ForgetPassword(AccountCondition condition);
}
實作 AccountRepository
public class AccountRepository : IAccountRepository
{
private readonly AccountContext _accountContext;
/// <summary>
///
/// </summary>
/// <param name="accountContext"></param>
public AccountRepository(AccountContext accountContext)
{
this._accountContext = accountContext;
}
/// <summary>
/// 新增帳號
/// </summary>
/// <param name="condition"></param>
/// <returns></returns>
public bool AddAccount(AccountCondition condition)
{
this._accountContext.Add(condition);
var result = this._accountContext.SaveChanges();
return result > 0;
}
/// <summary>
/// 忘記密碼
/// </summary>
/// <param name="condition"></param>
/// <returns></returns>
public bool ForgetPassword(AccountCondition condition)
{
var data = this._accountContext.Users.Find(condition.Account);
this._accountContext.Entry(data).CurrentValues.SetValues(condition);
var result = this._accountContext.SaveChanges();
return result > 0;
}
/// <summary>
/// 取得單筆帳號
/// </summary>
/// <param name="account"></param>
/// <returns></returns>
public AccountCondition GetAccount(string account)
{
var result = this._accountContext.Users.Find(account);
return result;
}
/// <summary>
/// 取得帳號列表
/// </summary>
/// <returns></returns>
public IEnumerable<AccountCondition> GetAccountList()
{
var result = this._accountContext.Users.ToList();
return result;
}
/// <summary>
/// 刪除帳號
/// </summary>
/// <param name="account"></param>
/// <returns></returns>
public bool RemoveAccount(string account)
{
var data = this._accountContext.Users.Find(account);
this._accountContext.Remove(data);
var result = this._accountContext.SaveChanges();
return result > 0;
}
/// <summary>
/// 更新帳號
/// </summary>
/// <param name="condition"></param>
/// <returns></returns>
public bool UpdateAccount(AccountCondition condition)
{
var data = this._accountContext.Users.Find(condition.Account);
data.Email = condition.Email;
data.ModifyDate = condition.ModifyDate;
data.ModifyUser = condition.ModifyUser;
data.Phone = condition.Phone;
var result = this._accountContext.SaveChanges();
return result > 0;
}
}
接下來 Service 部分列出稍微不同的地方
/// <summary>
/// 更新帳號資訊
/// </summary>
/// <param name="info"></param>
/// <returns></returns>
public ResultDto UpdateAccount(AccountInfoModel info)
{
if (string.IsNullOrWhiteSpace(info.Account) ||
string.IsNullOrWhiteSpace(info.Password))
{
throw new Exception("請檢查輸入欄位,缺一不可!");
}
var accountInfo = this._accountRepository.GetAccount(info.Account);
if (string.IsNullOrWhiteSpace(accountInfo.Password))
{
return new ResultDto
{
Success = false,
Message = "請確認要更新的帳號!"
};
}
var convertPassword = ConverPassword(info.Account, info.Password);
if (accountInfo.Password != convertPassword)
{
return new ResultDto
{
Success = false,
Message = "請確認輸入的密碼是否與註冊時一致!"
};
}
var condition = this._mapper.Map<AccountCondition>(info);
condition.ModifyDate = DateTime.Now;
condition.ModifyUser = info.Account;
var result = this._accountRepository.UpdateAccount(condition);
return new ResultDto
{
Success = result,
Message = result ? "更新成功" : "更新失敗"
};
}
StartUp 部分
public void ConfigureServices(IServiceCollection services)
{
//注入註冊
services.AddScoped<IAccountService, AccountService>();
services.AddScoped<IAccountRepository, AccountRepository>();
services.AddDbContext<AccountContext>(
options => options.UseSqlServer(@"Server=localhost;Database=Northwind;Trusted_Connection=True;"));
//AutoMapper
services.AddAutoMapper(
typeof(ControllerProfile),
typeof(ServiceProfile));
services.AddControllers();
}
後續步驟有問題的請參考前幾篇。 同樣的我也會把程式碼同步放上GitHub歡迎取用參考。
四、重點筆記。
1、部分更新問題。
這邊遇到第一個大問題是原來 EF 並不希望你使用部分更新,因此一開始直接使用 update 馬上就報錯給我看,查了好幾種解決方式,我現在也不確定目前使用這種做法是否合適,若有更好的方法歡迎交流。
附上查詢到的 StackOverflow 連結:
Entity Framework validation with partial updates
2021/04/19更新
感謝同事指正,提供了新的部分更新做法。
原本寫法
/// <summary>
/// 更新帳號
/// </summary>
/// <param name="condition"></param>
/// <returns></returns>
public bool UpdateAccount(AccountCondition condition)
{
var data = this._accountContext.Users.Find(condition.Account);
data.Email = condition.Email;
data.ModifyDate = condition.ModifyDate;
data.ModifyUser = condition.ModifyUser;
data.Phone = condition.Phone;
var result = this._accountContext.SaveChanges();
return result > 0;
}
更新後寫法
/// <summary>
/// 更新帳號
/// </summary>
/// <param name="condition"></param>
/// <returns></returns>
public bool UpdateAccount(AccountCondition condition)
{
var data = this._accountContext.Users.FirstOrDefault(
rows => rows.Account == condition.Account);
data.Email = condition.Email;
data.ModifyDate = condition.ModifyDate;
data.ModifyUser = condition.ModifyUser;
data.Phone = condition.Phone;
this._accountContext.Update(data)
.Property(rows => rows.Idx).IsModified = false;
var result = this._accountContext.SaveChanges();
return result > 0;
}
測試:
2、Repository Pattern Unit of Work。
這邊也看到 EF 使用 Repository 時會在隔離一個 Unit of Work 出來,研究了大約一周,還沒多大感受(其實是我偏愛 Dapper),所以這邊就使用最簡單作法。
如有興趣研究的也附上連結:
後記
這次自己從頭到尾做一次 EF 版本的 API 有一種自斷手腳的感覺,平常用 Dapper 簡單 CRUD 不用三兩下就完成的,換了一個工具還真的體會轉換陣痛期。
但 EF 是 .NET 自家指定用法身為微軟全家餐的受害者(?,免不了還是要學習一下,在此提供弱弱筆記供大家參考。