从头编写 asp.net core 2.0 web api 基础框架 (5) EF CRUD
2017-10-16 16:32
956 查看
第1部分:http://www.cnblogs.com/cgzl/p/7637250.html
第2部分:http://www.cnblogs.com/cgzl/p/7640077.html
第3部分:http://www.cnblogs.com/cgzl/p/7652413.html
第4部分:http://www.cnblogs.com/cgzl/p/7661805.html
Github源码地址:https://github.com/solenovex/Building-asp.net-core-2-web-api-starter-template-from-scratch
这是第一大部分的最后一小部分。要完成CRUD的操作。
1.相关的一些代码到处重复,有可能在程序中很多地方我都会更新Product,那样的话我可能就会在多个Action里面写同样的代码,而比较好的做法是只在一个地方写更新Product的代码。
2.到处写重复代码还会导致另外一个问题,那就是容易出错。
3.还有就是难以测试,如果想对Controller的Action进行单元测试,但是这些Action还包含着持久化相关的逻辑,这就很难的精确的找出到底是逻辑出错还是持久化部分出错了。
所以如果能有一种方法可以mock持久化相关的代码,然后再测试,就会知道错误不是发生在持久化部分了,这就可以用Repository Pattern了。
Repository Pattern是一种抽象,它减少了复杂性,目标是使代码对repository的实现更安全,并且与持久化要无关。
其中持久化无关这点我要明确一下,有时候是指可以随意切换持久化的技术,但这实际上并不是repository pattern的目的,其真正的目的是可以为repository挑选一个最好的持久化技术。例如:创建一个Product最好的方式可能是使用entity framework,而查询product最好的方式可能是使用dapper,也有可能会调用外部服务,而对调用repository的消费者来说,它不关心这些具体的实现细节。
首先再建立一个Material entity,然后和Product做成多对一的关系:
修改Product.cs:
然后别忘了在Context里面注册Material的Configuration并添加DbSet属性:
然后添加一个迁移 Add-Migration AddMaterial:
View Code
运行一下都应该没有什么问题。
上面都是查询的Actions。
下面开始做CUD的映射更改。
AddProduct会把传进来的product添加到context的内存中(姑且这么说),但是还没有更新到数据库。
Save方法里面是把context所追踪的实体变化(CUD)更新到数据库。
然后把这两个方法提取到IProductRepository接口里:
修改Controller的Post:
注意别忘了要返回的是Dto。
运行:
没问题。
这里我们使用了Mapper.Map的另一个overload的方法,它有两个参数。这个方法会把第一个对象相应的值赋给第二个对象上。这时候product的state就变成了modified了。
然后保存即可。
试试:
试试:
没问题。
在Repository添加一个Delete方法:
提取到IProductRepository:
然后Controller:
运行:
Ok。
第一大部分先写到这。。。。。。。。。。。。
接下来几天比较忙,然后我再编写第二大部分。我会直接弄一个已经重构好的模板,简单讲一下,然后重点是Identity Server 4.
到目前为止可以进行CRUD操作了,接下来需要把项目重构一下,然后再简单用一下Identity Server4。
第2部分:http://www.cnblogs.com/cgzl/p/7640077.html
第3部分:http://www.cnblogs.com/cgzl/p/7652413.html
第4部分:http://www.cnblogs.com/cgzl/p/7661805.html
Github源码地址:https://github.com/solenovex/Building-asp.net-core-2-web-api-starter-template-from-scratch
这是第一大部分的最后一小部分。要完成CRUD的操作。
Repository Pattern
我们可以直接在Controller访问DbContext,但是可能会有一些问题:1.相关的一些代码到处重复,有可能在程序中很多地方我都会更新Product,那样的话我可能就会在多个Action里面写同样的代码,而比较好的做法是只在一个地方写更新Product的代码。
2.到处写重复代码还会导致另外一个问题,那就是容易出错。
3.还有就是难以测试,如果想对Controller的Action进行单元测试,但是这些Action还包含着持久化相关的逻辑,这就很难的精确的找出到底是逻辑出错还是持久化部分出错了。
所以如果能有一种方法可以mock持久化相关的代码,然后再测试,就会知道错误不是发生在持久化部分了,这就可以用Repository Pattern了。
Repository Pattern是一种抽象,它减少了复杂性,目标是使代码对repository的实现更安全,并且与持久化要无关。
其中持久化无关这点我要明确一下,有时候是指可以随意切换持久化的技术,但这实际上并不是repository pattern的目的,其真正的目的是可以为repository挑选一个最好的持久化技术。例如:创建一个Product最好的方式可能是使用entity framework,而查询product最好的方式可能是使用dapper,也有可能会调用外部服务,而对调用repository的消费者来说,它不关心这些具体的实现细节。
首先再建立一个Material entity,然后和Product做成多对一的关系:
namespace CoreBackend.Api.Entities { public class Material { public int Id { get; set; } public int ProductId { get; set; } public string Name { get; set; } public Product Product { get; set; } } public class MaterialConfiguration : IEntityTypeConfiguration<Material> { public void Configure(EntityTypeBuilder<Material> builder) { builder.HasKey(x => x.Id); builder.Property(x => x.Name).IsRequired().HasMaxLength(50); builder.HasOne(x => x.Product).WithMany(x => x.Materials).HasForeignKey(x => x.ProductId) .OnDelete(DeleteBehavior.Cascade); } } }
修改Product.cs:
namespace CoreBackend.Api.Entities { public class Product { public int Id { get; set; } public string Name { get; set; } public float Price { get; set; } public string Description { get; set; } public ICollection<Material> Materials { get; set; } } public class ProductConfiguration : IEntityTypeConfiguration<Product> { public void Configure(EntityTypeBuilder<Product> builder) { builder.HasKey(x => x.Id); builder.Property(x => x.Name).IsRequired().HasMaxLength(50); builder.Property(x => x.Price).HasColumnType("decimal(8,2)"); builder.Property(x => x.Description).HasMaxLength(200); } } }
然后别忘了在Context里面注册Material的Configuration并添加DbSet属性:
namespace CoreBackend.Api.Entities { public class MyContext : DbContext { public MyContext(DbContextOptions<MyContext> options) : base(options) { Database.Migrate(); } public DbSet<Product> Products { get; set; } public DbSet<Material> Materials { get; set; } protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.ApplyConfiguration(new ProductConfiguration()); modelBuilder.ApplyConfiguration(new MaterialConfiguration()); } } }
然后添加一个迁移 Add-Migration AddMaterial:
namespace CoreBackend.Api.Controllers { [Route("api/product")] // 和主Model的Controller前缀一样 public class MaterialController : Controller { private readonly IProductRepository _productRepository; public MaterialController(IProductRepository productRepository) { _productRepository = productRepository; } [HttpGet("{productId}/materials")] public IActionResult GetMaterials(int productId) { var product = _productRepository.ProductExist(productId); if (!product) { return NotFound(); } var materials = _productRepository.GetMaterialsForProduct(productId); var results = Mapper.Map<IEnumerable<MaterialDto>>(materials); return Ok(results); } [HttpGet("{productId}/materials/{id}")] public IActionResult GetMaterial(int productId, int id) { var product = _productRepository.ProductExist(productId); if (!product) { return NotFound(); } var material = _productRepository.GetMaterialForProduct(productId, id); if (material == null) { return NotFound(); } var result = Mapper.Map<MaterialDto>(material); return Ok(result); } } }
View Code
运行一下都应该没有什么问题。
上面都是查询的Actions。
下面开始做CUD的映射更改。
添加:
修改ProductRepository,添加以下方法:public void AddProduct(Product product) { _myContext.Products.Add(product); } public bool Save() { return _myContext.SaveChanges() >= 0; }
AddProduct会把传进来的product添加到context的内存中(姑且这么说),但是还没有更新到数据库。
Save方法里面是把context所追踪的实体变化(CUD)更新到数据库。
然后把这两个方法提取到IProductRepository接口里:
public interface IProductRepository { IEnumerable<Product> GetProducts(); Product GetProduct(int productId, bool includeMaterials); IEnumerable<Material> GetMaterialsForProduct(int productId); Material GetMaterialForProduct(int productId, int materialId); bool ProductExist(int productId); void AddProduct(Product product); bool Save(); }
修改Controller的Post:
[HttpPost] public IActionResult Post([FromBody] ProductCreation product) { if (product == null) { return BadRequest(); } if (product.Name == "产品") { ModelState.AddModelError("Name", "产品的名称不可以是'产品'二字"); } if (!ModelState.IsValid) { return BadRequest(ModelState); } var newProduct = Mapper.Map<Product>(product); _productRepository.AddProduct(newProduct); if (!_productRepository.Save()) { return StatusCode(500, "保存产品的时候出错"); } var dto = Mapper.Map<ProductWithoutMaterialDto>(newProduct); return CreatedAtRoute("GetProduct", new { id = dto.Id }, dto); }
注意别忘了要返回的是Dto。
运行:
没问题。
Put
cfg.CreateMap<ProductModification, Product>();
[HttpPut("{id}")] public IActionResult Put(int id, [FromBody] ProductModification productModificationDto) { if (productModificationDto == null) { return BadRequest(); } if (productModificationDto.Name == "产品") { ModelState.AddModelError("Name", "产品的名称不可以是'产品'二字"); } if (!ModelState.IsValid) { return BadRequest(ModelState); } var product = _productRepository.GetProduct(id); if (product == null) { return NotFound(); } Mapper.Map(productModificationDto, product); if (!_productRepository.Save()) { return StatusCode(500, "保存产品的时候出错"); } return NoContent(); }
这里我们使用了Mapper.Map的另一个overload的方法,它有两个参数。这个方法会把第一个对象相应的值赋给第二个对象上。这时候product的state就变成了modified了。
然后保存即可。
试试:
Partial Update
cfg.CreateMap<Product, ProductModification>();
[HttpPatch("{id}")] public IActionResult Patch(int id, [FromBody] JsonPatchDocument<ProductModification> patchDoc) { if (patchDoc == null) { return BadRequest(); } var productEntity = _productRepository.GetProduct(id); if (productEntity == null) { return NotFound(); } var toPatch = Mapper.Map<ProductModification>(productEntity); patchDoc.ApplyTo(toPatch, ModelState); if (!ModelState.IsValid) { return BadRequest(ModelState); } if (toPatch.Name == "产品") { ModelState.AddModelError("Name", "产品的名称不可以是'产品'二字"); } TryValidateModel(toPatch); if (!ModelState.IsValid) { return BadRequest(ModelState); } Mapper.Map(toPatch, productEntity); if (!_productRepository.Save()) { return StatusCode(500, "更新的时候出错"); } return NoContent(); }
试试:
没问题。
Delete
只是替换成repository,不涉及mapping。在Repository添加一个Delete方法:
public void DeleteProduct(Product product) { _myContext.Products.Remove(product); }
提取到IProductRepository:
void DeleteProduct(Product product);
然后Controller:
[HttpDelete("{id}")] public IActionResult Delete(int id) { var model = _productRepository.GetProduct(id); if (model == null) { return NotFound(); } _productRepository.DeleteProduct(model); if (!_productRepository.Save()) { return StatusCode(500, "删除的时候出错"); } _mailService.Send("Product Deleted",$"Id为{id}的产品被删除了"); return NoContent(); }
运行:
Ok。
第一大部分先写到这。。。。。。。。。。。。
接下来几天比较忙,然后我再编写第二大部分。我会直接弄一个已经重构好的模板,简单讲一下,然后重点是Identity Server 4.
到目前为止可以进行CRUD操作了,接下来需要把项目重构一下,然后再简单用一下Identity Server4。
相关文章推荐
- 从头编写 asp.net core 2.0 web api 基础框架 (5) EF CRUD
- 从头编写 asp.net core 2.0 web api 基础框架 (1)
- 从头编写 asp.net core 2.0 web api 基础框架 (3)
- 从头编写 asp.net core 2.0 web api 基础框架 (4) EF配置
- 从头编写 asp.net core 2.0 web api 基础框架 (2)
- 从头编写 asp.net core 2.0 web api 基础框架 (1)
- 从头编写 asp.net core 2.0 web api 基础框架 (4) EF配置
- 从头编写 asp.net core 2.0 web api 基础框架 (2)
- 从头编写 asp.net core 2.0 web api 基础框架 (3)
- 从头编写 asp.net core 2.0 web api 基础框架 (1)
- asp.net core 2.0 web api基于JWT自定义策略授权
- asp.net core 2.0 web api基于JWT自定义策略授权
- 简易的开发框架(微服务) Asp.Net Core 2.0
- IdentityServer4 ASP.NET Core的OpenID Connect OAuth 2.0框架学习保护API
- IdentityServer4 ASP.NET Core的OpenID Connect OAuth 2.0框架学习保护API
- ASP.NET Boilerplate 学习 AspNet Core2 浏览器缓存使用 c#基础,单线程,跨线程访问和线程带参数 wpf 禁用启用webbroswer右键菜单 EF Core 2.0使用MsSql/MySql实现DB First和Code First ASP.NET Core部署到Windows IIS QRCode.js:使用 JavaScript 生成
- IdentityServer4 ASP.NET Core的OpenID Connect OAuth 2.0框架学习保护API
- 在 Asp.net core 2.0 的Web Api 添加logging
- 基础教程:ASP.NET Core 2.0 MVC筛选器
- 基础教程:上传/下载ASP.NET Core 2.0中的文件