一,引言
接著上一篇使用 EF Core 操作 Azure CosmosDB 生成種子資料,今天我們完成通過 EF Core 實現CRUD一系列功能。EF Core 3.0 提供了CosmosDB 資料庫提供程式的第一個可用的版本,今天我們使用 EF Core 3.1在嘗試使用Cosmos DB 來儲存和構建 Asp.NET Core 應用程式時,可能還有一些差異。
1,CosmosDB 不會生成唯一的主鍵,Cosmos DB不會像SQL資料庫那樣建立主鍵。如果需要新增到 Cosmos DB中,則可能會使用類似GUID 字串的名稱。
2,Cosmos DB 不支援EF Core 遷移
--------------------我是分割線--------------------
1,Azure Cosmos DB (一) 入門介紹
2,Azure Cosmos DB (二) SQL API 操作
3,Azure Cosmos DB (三) EF Core 操作CURD Demo
二,正文
1,Repository 基類倉儲實現類
將之前建立好的UserContext 注入基類倉儲中
1 /// <summary> 2 /// 基類倉儲 3 /// </summary> 4 public abstract class Repository<TEntity> : IRepository<TEntity> where TEntity : class,new() 5 { 6 protected DbContext Db; 7 8 public async virtual Task<TEntity> GetById(string partitionKey) 9 { 10 return await Db.Set<TEntity>().FindAsync( partitionKey); 11 } 12 13 public async virtual Task<TEntity> Add(TEntity entity) 14 { 15 await Db.AddAsync<TEntity>(entity); 16 return entity; 17 } 18 19 public virtual bool Update(TEntity entity) 20 { 21 Db.Add(entity); 22 Db.Entry(entity).State = EntityState.Modified; 23 return true; 24 } 25 26 public virtual bool Remove(TEntity entity) 27 { 28 Db.Set<TEntity>().Remove(entity); 29 return true; 30 } 31 32 public virtual IEnumerable<TEntity> GetAll() 33 { 34 return Db.Set<TEntity>(); 35 } 36 37 public virtual IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> conditions) 38 { 39 return Db.Set<TEntity>().Where(conditions); 40 } 41 42 public async Task<int> SaveChangesAsync() 43 { 44 return await Db.SaveChangesAsync(); 45 } 46 47 public int SaveChanges() 48 { 49 return Db.SaveChanges(); 50 } 51 52 public void Dispose() 53 { 54 Db.Dispose(); 55 GC.SuppressFinalize(this); 56 } 57 }
1 public interface IRepository<TEntity> : IDisposable where TEntity : class 2 { 3 /// <summary> 4 /// 根據Id獲取物件 5 /// </summary> 6 /// <param name="partitionKey"></param> 7 /// <returns></returns> 8 Task<TEntity> GetById(string partitionKey); 9 10 /// <summary> 11 /// 新增 12 /// </summary> 13 /// <param name="entity"></param> 14 Task<TEntity> Add(TEntity entity); 15 16 /// <summary> 17 /// 更新 18 /// </summary> 19 /// <param name="entity"></param> 20 bool Update(TEntity entity); 21 22 /// <summary> 23 /// 刪除 24 /// </summary> 25 /// <param name="entity"></param> 26 bool Remove(TEntity entity); 27 28 /// <summary> 29 /// 查詢全部 30 /// </summary> 31 /// <returns></returns> 32 IEnumerable<TEntity> GetAll(); 33 34 /// <summary> 35 /// 查詢根據條件 36 /// </summary> 37 /// <param name="conditions"></param> 38 /// <returns></returns> 39 IEnumerable<TEntity> Find(Expression<Func<TEntity, bool>> conditions); 40 41 42 /// <summary> 43 /// 儲存 44 /// </summary> 45 /// <returns></returns> 46 Task<int> SaveChangesAsync(); 47 48 49 /// <summary> 50 /// 儲存 51 /// </summary> 52 /// <returns></returns> 53 int SaveChanges(); 54 }
1 /// <summary> 2 /// IUserRepository介面 3 /// </summary> 4 public interface IUserRepository : IRepository<UserModel> 5 { 6 //一些UserModel獨有的介面 7 Task<UserModel> GetByName(string name); 8 }
1 public class UserRepository : Repository<UserModel>, IUserRepository 2 { 3 public UserRepository(UserContext context) 4 { 5 Db = context; 6 } 7 8 #region 01,獲取使用者根據姓名+async Task<UserModel> GetByName(string name) 9 /// <summary> 10 /// 獲取使用者根據姓名 11 /// </summary> 12 /// <param name="name">姓名</param> 13 /// <returns></returns> 14 public async Task<UserModel> GetByName(string name) 15 { 16 //返回一個新查詢,其中返回的實體將不會在 System.Data.Entity.DbContext 中進行快取 17 return await Db.Set<UserModel>().AsNoTracking().FirstOrDefaultAsync(c => c.Name == name); 18 } 19 #endregion 20 }
注意,在更新操作中,有個小坑
每個專案都必須具有 id 給定分割槽鍵唯一的值。預設情況下,EF Core通過使用“ |”將區分符和主鍵值連線起來來生成該值。作為分隔符。僅當實體進入 Added 狀態時才生成鍵值。如果實體 id
在.NET型別上沒有屬性來儲存值,則在附加實體時可能會出現問題。
將實體標記為首先新增,然後將其更改為所需狀態,我這裡將狀態改為 “Modified”
public virtual bool Update(TEntity entity) { Db.Add(entity); Db.Entry(entity).State = EntityState.Modified; return true; }
以下是整個倉儲層的結構
2,實現業務層 Service 方法
2.1,這裡我們的業務層的模型檢視為 “UserViewModel”,但是我們的倉儲使用的是實體資料模型 "UserModel",這裡我引用 Automapper,進行物件轉化 。
使用程式包管理控制檯進行安裝
Install-Package AutoMapper -Version 10.0.0
Install-Package AutoMapper.Extensions.Microsoft.DependencyInjection -Version 8.0.1
2.2,配置構建函式,用來建立關係對映
public DomainToViewModelMappingProfile() { CreateMap<UserModel, UserViewModel>(); }
public ViewModelToDomainMappingProfile() { //手動進行配置 CreateMap<UserViewModel, UserModel>(); }
全域性 AutoMapper 配置檔案
/// <summary> /// 靜態全域性 AutoMapper 配置檔案 /// </summary> public class AutoMapperConfig { public static MapperConfiguration RegisterMappings() { //建立AutoMapperConfiguration, 提供靜態方法Configure,一次載入所有層中Profile定義 //MapperConfiguration例項可以靜態儲存在一個靜態欄位中,也可以儲存在一個依賴注入容器中。 一旦建立,不能更改/修改。 return new MapperConfiguration(cfg => { //這個是領域模型 -> 檢視模型的對映,是 讀命令 cfg.AddProfile(new DomainToViewModelMappingProfile()); //這裡是檢視模型 -> 領域模式的對映,是 寫 命令 cfg.AddProfile(new ViewModelToDomainMappingProfile()); }); } }
2.3,建立UserService 實現類,IApplicationService、IUserService 介面
1 /// <summary> 2 /// UserService 服務介面實現類,繼承 服務介面 3 /// </summary> 4 public class UserService : IUserService 5 { 6 private readonly IUserRepository _UserRepository; 7 // 用來進行DTO 8 private readonly IMapper _mapper; 9 10 public void Dispose() 11 { 12 GC.SuppressFinalize(this); 13 } 14 15 public UserService( 16 IUserRepository userRepository, 17 IMapper mapper) 18 { 19 _UserRepository = userRepository; 20 _mapper = mapper; 21 } 22 23 public IEnumerable<UserViewModel> GetAll() 24 { 25 //第一種寫法 Map 26 return _mapper.Map<IEnumerable<UserViewModel>>(_UserRepository.GetAll()); 27 } 28 29 public UserViewModel GetById(string partitionKey) 30 { 31 return _mapper.Map<UserViewModel>(_UserRepository.GetById(partitionKey).Result); 32 } 33 34 public async Task<int> Register(UserViewModel userViewModel) 35 { 36 var partitionKey = _UserRepository.GetAll().Max(x => int.Parse(x.PartitionKey)); 37 userViewModel.PartitionKey = (++partitionKey).ToString(); 38 await _UserRepository.Add(_mapper.Map<UserModel>(userViewModel)); 39 return await _UserRepository.SaveChangesAsync(); 40 } 41 42 public void Remove(string partitionKey) 43 { 44 45 _UserRepository.Remove(_mapper.Map<UserModel>(_UserRepository.GetById(partitionKey).Result)); 46 _UserRepository.SaveChangesAsync(); 47 } 48 49 public int Update(UserViewModel userViewModel) 50 { 51 _UserRepository.Update(_mapper.Map<UserModel>(userViewModel)); 52 return _UserRepository.SaveChanges(); 53 } 54 }
1 public interface IApplicationService<T> where T : class, new() 2 { 3 /// <summary> 4 /// 獲取全部資料 5 /// </summary> 6 /// <returns></returns> 7 IEnumerable<T> GetAll(); 8 9 /// <summary> 10 /// 獲取單個資料 11 /// </summary> 12 /// <param name="partitionKey"></param> 13 /// <returns></returns> 14 T GetById(string partitionKey); 15 16 /// <summary> 17 /// 更新資料 18 /// </summary> 19 /// <param name="viewmodel"></param> 20 int Update(T viewmodel); 21 22 /// <summary> 23 /// 刪除資料 24 /// </summary> 25 /// <param name="partitionKey"></param> 26 void Remove(string partitionKey); 27 }
1 public interface IUserService:IApplicationService<UserViewModel> 2 { 3 /// <summary> 4 /// 註冊 5 /// </summary> 6 /// <param name="userViewModel"></param> 7 Task<int> Register(UserViewModel userViewModel); 8 }
3,建立 UserController 的CRUD 方法及頁面
3.1,使用者列表,使用者詳情控制器方法
// GET: User public ActionResult Index() { return View(_userService.GetAll()); } // GET: User/Details/5 public ActionResult Details(string partitionKey) { try { // TODO: Add insert logic here // 執行查詢方法 var userViewModel= _userService.GetById(partitionKey); return View(userViewModel); } catch { return View(); } }
3.2 使用者列表,使用者資訊詳細頁面
Index.cshtml(使用者列表)
@model IEnumerable<Azure.CosmosDB.Models.UserViewModel> @{ ViewData["Title"] = "Index"; } <h1>Index</h1> <p> <a asp-action="Create">Create New</a> </p> <table class="table"> <thead> <tr> <th> @Html.DisplayNameFor(model => model.Id) </th> <th> @Html.DisplayNameFor(model => model.Name) </th> <th> @Html.DisplayNameFor(model => model.Age) </th> <th> @Html.DisplayNameFor(model => model.Address) </th> <th></th> </tr> </thead> <tbody> @foreach (var item in Model) { <tr> <td> @Html.DisplayFor(modelItem => item.Id) </td> <td> @Html.DisplayFor(modelItem => item.Name) </td> <td> @Html.DisplayFor(modelItem => item.Age) </td> <td> @Html.DisplayFor(modelItem => item.Address) </td> <td> @Html.ActionLink("Edit", "Edit", new { partitionKey = item.PartitionKey }) | @Html.ActionLink("Details", "Details", new { partitionKey = item.PartitionKey }) | @Html.ActionLink("Delete", "Delete", new { partitionKey = item.PartitionKey }) </td> </tr> } </tbody> </table>
Details.cshtml(使用者詳情)
@model Azure.CosmosDB.Models.UserViewModel @{ ViewData["Title"] = "Details"; Layout = "~/Views/Shared/_Layout.cshtml"; } <h1>Details</h1> <div> <h4>UserViewModel</h4> <hr /> <dl class="row"> <dt class = "col-sm-2"> @Html.DisplayNameFor(model => model.PartitionKey) </dt> <dd class = "col-sm-10"> @Html.DisplayFor(model => model.PartitionKey) </dd> <dt class = "col-sm-2"> @Html.DisplayNameFor(model => model.Id) </dt> <dd class = "col-sm-10"> @Html.DisplayFor(model => model.Id) </dd> <dt class = "col-sm-2"> @Html.DisplayNameFor(model => model.Name) </dt> <dd class = "col-sm-10"> @Html.DisplayFor(model => model.Name) </dd> <dt class = "col-sm-2"> @Html.DisplayNameFor(model => model.Age) </dt> <dd class = "col-sm-10"> @Html.DisplayFor(model => model.Age) </dd> <dt class = "col-sm-2"> @Html.DisplayNameFor(model => model.Address) </dt> <dd class = "col-sm-10"> @Html.DisplayFor(model => model.Address) </dd> </dl> </div> <div> @Html.ActionLink("Edit", "Edit", new { partitionKey = Model.PartitionKey }) | <a asp-action="Index">Back to List</a> </div>
4,依賴注入倉儲,服務模型以及AutoMap 配置等
// 注入 應用層Application services.AddScoped<IUserService, UserService>(); // 注入 基礎設施層 - 資料層 services.AddScoped<IUserRepository, UserRepository>(); //新增服務 services.AddAutoMapper(typeof(AutoMapperConfig)); //啟動配置 AutoMapperConfig.RegisterMappings();
5,配置分割槽鍵(分割槽鍵屬性可以是任何型別,只要將其轉換為string)
預設情況下,EF Core將在建立分割槽時將分割槽鍵設定為的容器 "_partitionkey" 而不為其提供任何值。但是要充分利用 Azure Cosmos 的效能,應使用經過精心選擇的分割槽鍵。可以通過呼叫HasPartitionKey進行配置:
//配置分割槽鍵 modelBuilder.Entity<UserModel>() .HasPartitionKey(o => o.PartitionKey);
執行專案,可以看到使用者列表資訊,同時點選資料的 “Details” 可以看到使用者資料詳情資訊。
ok,這裡就不再演示執行後,測試各個增刪改查的方法,大家可以下載程式碼,配置本地環境,執行程式碼進行測試
撒花?????,今天的分析到此結束。
三,結尾
寫這篇文章,花了我比預期更長的時間,主要耗時用在寫Demo 上,查文件,寫測試等。希望對大家有用。當然也可以加深我們對Cosmos DB可以做什麼以及EF Core 3.1 如何操作 Cosmos DB 資料庫所提供程式的有進一步的瞭解。
如果沒有大家沒有條件在Azure 上建立Cosmos DB 進行測試,學習,可以使用 “Azure Cosmos DB 模擬器”,模擬器在本地執行,並使用localdb儲存結果。它還帶有一個不錯的介面,用於檢視/編輯資料。
github:https://github.com/yunqian44/Azure.CosmosDB.git
作者:Allen
版權:轉載請在文章明顯位置註明作者及出處。如發現錯誤,歡迎批評指正。