您的位置:首页 > 编程语言 > ASP

从头编写 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的操作。

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。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: