Azure Cosmos DB (三) EF Core 實現 CURD Demo


一,引言

  接着上一篇使用 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

4,Azure Cosmos DB (四) 使用EF的SQL API 異地冗余

二,正文

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     }
Repository.cs
 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     }
IRepository.cs
1     /// <summary>
2     /// IUserRepository接口
3     /// </summary>
4     public interface IUserRepository : IRepository<UserModel>
5     {
6         //一些UserModel獨有的接口
7         Task<UserModel> GetByName(string name);
8     }
IUserRepository.cs
 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     }
UserRepository.cs

注意,在更新操作中,有個小坑

每個項目都必須具有 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     }
UserService.cs
 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     }
IApplicationService.cs
1 public interface IUserService:IApplicationService<UserViewModel>
2     {
3         /// <summary>
4         /// 注冊
5         /// </summary>
6         /// <param name="userViewModel"></param>
7         Task<int> Register(UserViewModel userViewModel);
8     }
IUserService.cs

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 

版權:轉載請在文章明顯位置注明作者及出處。如發現錯誤,歡迎批評指正。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM