【转载】从头编写 asp.net core 2.0 web api 基础框架 (5) EF CRUD

 

Github源码地址:https://github.com/solenovex/Building-asp.net-core-2-web-api-starter-template-from-scratchjavascript

这是第一大部分的最后一小部分。要完成CRUD的操做。java

Repository Pattern

咱们能够直接在Controller访问DbContext,可是可能会有一些问题:git

1.相关的一些代码处处重复,有可能在程序中不少地方我都会更新Product,那样的话我可能就会在多个Action里面写一样的代码,而比较好的作法是只在一个地方写更新Product的代码。github

2.处处写重复代码还会致使另一个问题,那就是容易出错。web

3.还有就是难以测试,若是想对Controller的Action进行单元测试,可是这些Action还包含着持久化相关的逻辑,这就很难的精确的找出究竟是逻辑出错仍是持久化部分出错了。数据库

因此若是能有一种方法能够mock持久化相关的代码,而后再测试,就会知道错误不是发生在持久化部分了,这就能够用Repository Pattern了。api

Repository Pattern是一种抽象,它减小了复杂性,目标是使代码对repository的实现更安全,而且与持久化要无关安全

其中持久化无关这点我要明确一下,有时候是指能够随意切换持久化的技术,但这实际上并非repository pattern的目的,其真正的目的是能够为repository挑选一个最好的持久化技术。例如:建立一个Product最好的方式多是使用entity framework,而查询product最好的方式多是使用dapper,也有可能会调用外部服务,而对调用repository的消费者来讲,它不关心这些具体的实现细节。app

首先再创建一个Material entity,而后和Product作成多对一的关系:asp.net

复制代码
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:

而后数据库直接进行迁移操做了,无需再作update-database。

 

创建一个Repositories文件夹,添加一个IProductRepository:

复制代码
namespace CoreBackend.Api.Repositories
{
    public interface IProductRepository
    {
        IEnumerable<Product> GetProducts();
        Product GetProduct(int productId, bool includeMaterials);
        IEnumerable<Material> GetMaterialsForProduct(int productId);
        Material GetMaterialForProduct(int productId, int materialId);
    }
}
复制代码

这个是ProductRepository将要实现的接口,里面定义了一些必要的方法:查询Products,查询单个Product,查询Product的Materials和查询Product下的一个Material。

其中相似GetProducts()这样的方法返回类型仍是有争议的,IQueryable<T>仍是IEnumerable<T>。

若是返回的是IQueryable,那么调用repository的地方还能够继续构建IQueryable,例如在真正的查询执行以前附加一个OrderBy或者Where方法。可是这样作的话,也意味着你把持久化相关的代码给泄露出去了,这看起来是违反了repository pattern的目的。

若是是IEnumerable,为了返回各类各样状况的查询结果,须要编写几十个上百个查询方法,那也是至关繁琐的,几乎是不可能的。

目前看来,两种返回方式都有人在用,因此根据状况定吧。咱们的程序需求比较简单,因此使用IEnumerable。

而后创建具体的实现类 ProductRepository:

复制代码
namespace CoreBackend.Api.Repositories
{
    public class ProductRepository : IProductRepository
    {
        private readonly MyContext _myContext;

        public ProductRepository(MyContext myContext)
        {
            _myContext = myContext;
        }

        public IEnumerable<Product> GetProducts()
        {
            return _myContext.Products.OrderBy(x => x.Name).ToList();
        }

        public Product GetProduct(int productId, bool includeMaterials)
        {
            if (includeMaterials)
            {
                return _myContext.Products
                    .Include(x => x.Materials).FirstOrDefault(x => x.Id == productId);
            }
            return _myContext.Products.Find(productId);
        }

        public IEnumerable<Material> GetMaterialsForProduct(int productId)
        {
            return _myContext.Materials.Where(x => x.ProductId == productId).ToList();
        }

        public Material GetMaterialForProduct(int productId, int materialId)
        {
            return _myContext.Materials.FirstOrDefault(x => x.ProductId == productId && x.Id == materialId);
        }
    }
}
复制代码

这里面要包含吃就会的逻辑,因此咱们须要MyContext(也有可能须要其余的Service)那就在Constructor里面注入一个。重要的是调用的程序不关心这些细节。

这里也是编写额外的持久化逻辑的地方,好比说查询以后作个排序之类的。

(具体的Entity Framework Core的方法请查阅EF Core官方文档:https://docs.microsoft.com/en-us/ef/core/

GetProducts,查询全部的产品并按照名称排序并返回查询结果。这里注意必定要加上ToList(),它保证了对数据库的查询就在此时此刻发生。

GetProduct,查询单个产品,判断一下是否须要把产品下面的原料都一块儿查询出来,若是须要的话就使用Include这个extension method。查询条件能够放在FirstOrDefault()方法里面。

GetMaterialsForProduct,查询某个产品下全部的原料。

GetMaterialForProduct,查询某个产品下的某种原料。

创建好Repository以后,须要在Startup里面进行注册:

复制代码
        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
#if DEBUG
            services.AddTransient<IMailService, LocalMailService>();
#else
            services.AddTransient<IMailService, CloudMailService>();
#endif
            var connectionString = Configuration["connectionStrings:productionInfoDbConnectionString"];
            services.AddDbContext<MyContext>(o => o.UseSqlServer(connectionString));

            services.AddScoped<IProductRepository, ProductRepository>();
        }
复制代码

针对Repository,最好的生命周期是Scoped(每一个请求生成一个实例)。<>里面前边是它的合约接口,后边是具体实现。

使用Repository

先为ProductDto添加一个属性:

复制代码
namespace CoreBackend.Api.Dtos
{
    public class ProductDto
    {
        public ProductDto()
        {
            Materials = new List<MaterialDto>();
        }

        public int Id { get; set; }
        public string Name { get; set; }
        public float Price { get; set; }
        public string Description { get; set; }
        public ICollection<MaterialDto> Materials { get; set; }

        public int MaterialCount => Materials.Count;
    }
}
复制代码

就是返回该产品所用的原料个数。

再创建一个ProductWithoutMaterialDto:

复制代码
namespace CoreBackend.Api.Dtos
{
    public class ProductWithoutMaterialDto
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public float Price { get; set; }
        public string Description { get; set; }
    }
}
复制代码

这个Dto不带原料相关的导航属性。

而后修改controller。

如今咱们可使用ProductRepository替代原来的内存数据了,首先在ProductController里面注入ProductRepository:

复制代码
   public class ProductController : Controller
    {
        private readonly ILogger<ProductController> _logger;
        private readonly IMailService _mailService;
        private readonly IProductRepository _productRepository;

        public ProductController(
            ILogger<ProductController> logger,
            IMailService mailService,
            IProductRepository productRepository)
        {
            _logger = logger;
            _mailService = mailService;
            _productRepository = productRepository;
        }
复制代码

1.修改GetProducts这个Action:

复制代码
        [HttpGet]
        public IActionResult GetProducts()
        {
            var products = _productRepository.GetProducts();
            var results = new List<ProductWithoutMaterialDto>();
            foreach (var product in products)
            {
                results.Add(new ProductWithoutMaterialDto
                {
                    Id = product.Id,
                    Name = product.Name,
                    Price = product.Price,
                    Description = product.Description
                });
            }
            return Ok(results);
        }
复制代码

注意,其中的Product类型是DbContext和repository操做的类型,而不是Action应该返回的类型,并且咱们的查询结果是不带Material的,因此须要把Product的list映射成ProductWithoutMaterialDto的list。

而后试试:

查询的时候报错,是由于Product的属性Price,在fluentapi里面设置的类型是decimal(8, 2),而Price的类型是float,那么咱们把全部的Price的类型都改为decimal:

复制代码
    public class Product
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public string Description { get; set; }
        public ICollection<Material> Materials { get; set; }
    }

    public class ProductCreation
    {
        [Display(Name = "产品名称")]
        [Required(ErrorMessage = "{0}是必填项")]
        [StringLength(10, MinimumLength = 2, ErrorMessage = "{0}的长度应该不小于{2}, 不大于{1}")]
        public string Name { get; set; }

        [Display(Name = "价格")]
        [Range(0, Double.MaxValue, ErrorMessage = "{0}的值必须大于{1}")]
        public decimal Price { get; set; }

        [Display(Name = "描述")]
        [MaxLength(100, ErrorMessage = "{0}的长度不能够超过{1}")]
        public string Description { get; set; }
    }

public class ProductDto
    {
        public ProductDto()
        {
            Materials = new List<MaterialDto>();
        }

        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public string Description { get; set; }
        public ICollection<MaterialDto> Materials { get; set; }

        public int MaterialCount => Materials.Count;
    }

public class ProductModification
    {
        [Display(Name = "产品名称")]
        [Required(ErrorMessage = "{0}是必填项")]
        [StringLength(10, MinimumLength = 2, ErrorMessage = "{0}的长度应该不小于{2}, 不大于{1}")]
        public string Name { get; set; }

        [Display(Name = "价格")]
        [Range(0, Double.MaxValue, ErrorMessage = "{0}的值必须大于{1}")]
        public decimal Price { get; set; }

        [Display(Name = "描述")]
        [MaxLength(100, ErrorMessage = "{0}的长度不能够超过{1}")]
        public string Description { get; set; }
    }

public class ProductWithoutMaterialDto
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public decimal Price { get; set; }
        public string Description { get; set; }
    }
复制代码

还有SeedData里面和即将废弃的ProductService:

namespace CoreBackend.Api.Entities
{
    public static class MyContextExtensions
    {
        public static void EnsureSeedDataForContext(this MyContext context)
        {
            if (context.Products.Any())
            {
                return;
            }
            var products = new List<Product>
            {
                new Product
                {
                    Name = "牛奶",
                    Price = new decimal(2.5),
                    Description = "这是牛奶啊"
                },
                new Product
                {
                    Name = "面包",
                    Price = new decimal(4.5),
                    Description = "这是面包啊"
                },
                new Product
                {
                    Name = "啤酒",
                    Price = new decimal(7.5),
                    Description = "这是啤酒啊"
                }
            };
            context.Products.AddRange(products);
            context.SaveChanges();
        }
    }
}

namespace CoreBackend.Api.Services
{
    public class ProductService
    {
        public static ProductService Current { get; } = new ProductService();

        public List<ProductDto> Products { get; }

        private ProductService()
        {
            Products = new List<ProductDto>
            {
                new ProductDto
                {
                    Id = 1,
                    Name = "牛奶",
                    Price = new decimal(2.5),
                    Materials = new List<MaterialDto>
                    {
                        new MaterialDto
                        {
                            Id = 1,
                            Name = ""
                        },
                        new MaterialDto
                        {
                            Id = 2,
                            Name = "奶粉"
                        }
                    },
                    Description = "这是牛奶啊"
                },
                new ProductDto
                {
                    Id = 2,
                    Name = "面包",
                    Price = new decimal(4.5),
                    Materials = new List<MaterialDto>
                    {
                        new MaterialDto
                        {
                            Id = 3,
                            Name = "面粉"
                        },
                        new MaterialDto
                        {
                            Id = 4,
                            Name = ""
                        }
                    },
                    Description = "这是面包啊"
                },
                new ProductDto
                {
                    Id = 3,
                    Name = "啤酒",
                    Price = new decimal(7.5),
                    Materials = new List<MaterialDto>
                    {
                        new MaterialDto
                        {
                            Id = 5,
                            Name = "麦芽"
                        },
                        new MaterialDto
                        {
                            Id = 6,
                            Name = "地下水"
                        }
                    },
                    Description = "这是啤酒啊"
                }
            };
        }
    }
}
View Code

而后在运行试试:

结果正确。

而后修改GetProduct:

复制代码
[Route("{id}", Name = "GetProduct")]
        public IActionResult GetProduct(int id, bool includeMaterial = false)
        {
            var product = _productRepository.GetProduct(id, includeMaterial);
            if (product == null)
            {
                return NotFound();
            }
            if (includeMaterial)
            {
                var productWithMaterialResult = new ProductDto
                {
                    Id = product.Id,
                    Name = product.Name,
                    Price = product.Price,
                    Description = product.Description
                };
                foreach (var material in product.Materials)
                {
                    productWithMaterialResult.Materials.Add(new MaterialDto
                    {
                        Id = material.Id,
                        Name = material.Name
                    });
                }
                return Ok(productWithMaterialResult);
            }

            var onlyProductResult = new ProductDto
            {
                Id = product.Id,
                Name = product.Name,
                Price = product.Price,
                Description = product.Description
            };
            return Ok(onlyProductResult);
        }
复制代码

首先再添加一个参数includeMaterial表示是否带着Material表的数据一块儿查询出来,该参数有一个默认值是false,就是请求的时候若是不带这个参数,那么这个参数的值就是false。

经过repository查询以后把Product和Material分别映射成ProductDto和MaterialDot。

试试,首先不包含Material:

目前数据库的Material表没有数据,能够手动添加几个,也能够把数据库的Product数据删了,改一下种子数据那部分代码:

namespace CoreBackend.Api.Entities
{
    public static class MyContextExtensions
    {
        public static void EnsureSeedDataForContext(this MyContext context)
        {
            if (context.Products.Any())
            {
                return;
            }
            var products = new List<Product>
            {
                new Product
                {
                    Name = "牛奶",
                    Price = new decimal(2.5),
                    Description = "这是牛奶啊",
                    Materials = new List<Material>
                    {
                        new Material
                        {
                            Name = ""
                        },
                        new Material
                        {
                            Name = "奶粉"
                        }
                    }
                },
                new Product
                {
                    Name = "面包",
                    Price = new decimal(4.5),
                    Description = "这是面包啊",
                    Materials = new List<Material>
                    {
                        new Material
                        {
                            Name = "面粉"
                        },
                        new Material
                        {
                            Name = ""
                        }
                    }
                },
                new Product
                {
                    Name = "啤酒",
                    Price = new decimal(7.5),
                    Description = "这是啤酒啊",
                    Materials = new List<Material>
                    {
                        new Material
                        {
                            Name = "麦芽"
                        },
                        new Material
                        {
                            Name = "地下水"
                        }
                    }
                }
            };
            context.Products.AddRange(products);
            context.SaveChanges();
        }
    }
}
View Code

而后再试试GetProduct带有material的查询:

其中inludeMaterail这个参数须要使用query string的方式,也就是在uri后边加一个问号,问号后边跟着参数名,而后是等号,而后是它的值。若是有多个query string的参数,那么每组参数之间用&分开。

而后再修改一下MaterialController:

复制代码
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 materials = _productRepository.GetMaterialsForProduct(productId);
            var results = materials.Select(material => new MaterialDto
                {
                    Id = material.Id,
                    Name = material.Name
                })
                .ToList();
            return Ok(results);
        }

        [HttpGet("{productId}/materials/{id}")]
        public IActionResult GetMaterial(int productId, int id)
        {
            var material = _productRepository.GetMaterialForProduct(productId, id);
            if (material == null)
            {
                return NotFound();
            }
            var result = new MaterialDto
            {
                Id = material.Id,
                Name = material.Name
            };
            return Ok(result);
        }
    }
}
复制代码

注意GetMaterials方法内,咱们往productRepository的GetMaterialsForProduct传进去一个productId,若是repository返回的是空list可能会有两种状况:1 product不存在,2 product存在,而它没有下属的material。若是是第一种状况,那么应该返回的是404 NotFound,而第二种action应该返回一个空list。因此咱们须要一个方法判断product是否存在,因此打开ProductRepository,添加方法:

        public bool ProductExist(int productId)
        {
            return _myContext.Products.Any(x => x.Id == productId);
        }

并在pull up member(右键点击方法代码--重构里面有)到接口里面:

复制代码
namespace CoreBackend.Api.Repositories
{
    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);
    }
}
复制代码

而后再改一下Controller:

复制代码
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 = materials.Select(material => new MaterialDto
                {
                    Id = material.Id,
                    Name = material.Name
                })
                .ToList();
            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 = new MaterialDto
            {
                Id = material.Id,
                Name = material.Name
            };
            return Ok(result);
        }
    }
}
复制代码

试试:

结果都没有问题!!!

可是看看上面controller里面的代码,处处都是映射,这种手写的映射很容易出错,若是entity有几十个属性,而后在多个地方须要进行映射,那么这么写实在太糟糕了。

因此须要使用一个映射的库:

AutoMapper

 autoMapper是最主流的.net映射库,因此咱们用它。

经过nuget安装automapper:

安装完以后,首先要配置automapper。咱们要告诉automapper哪些entity和dto之间有映射关系。这个配置应该只建立一次,而且在startup的时候进行初始化。

在Startup的Configure方法添加:

复制代码
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory,
            MyContext myContext)
        {
            loggerFactory.AddNLog();
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            else
            {
                app.UseExceptionHandler();
            }
            myContext.EnsureSeedDataForContext();
            app.UseStatusCodePages();

            AutoMapper.Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<Product, ProductWithoutMaterialDto>(); }); 
            app.UseMvc();
        }
复制代码

建立映射关系,咱们须要使用AutoMapper.Mapper.Initialize方法,其参数是一个Action,这个Action的参数是一个Mapping Configuration。

cfg.CreateMap<Product, ProductWithoutMaterialDto>(),意思就是建立一个从Product到ProductWIthoutMaterialDto的映射关系。

AutoMapper是基于约定的,原对象的属性值会被映射到目标对象相同属性名的属性上。若是属性不存在,那么就忽略它。

偶尔咱们可能须要对AutoMapper的映射进行一些微调,可是对于大多数状况来讲,上面这一句话就够用了。

如今能够在controller里面使用这个映射了。

打开controller首先改一下GetProducts:

复制代码
        [HttpGet]
        public IActionResult GetProducts()
        {
            var products = _productRepository.GetProducts();
            var results = Mapper.Map<IEnumerable<ProductWithoutMaterialDto>>(products);
            return Ok(results);
        }
复制代码

使用Mapper.Map进行映射,<T>其中T是目标类型,能够是一个model也能够是一个集合,括号里面的参数是原对象们。

运行试试:

没问题,结果和以前是同样的。

而后针对GetProduct,首先再创建一对映射:

            AutoMapper.Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<Product, ProductWithoutMaterialDto>();
                cfg.CreateMap<Product, ProductDto>();
            });

而后GetProduct:

复制代码
[Route("{id}", Name = "GetProduct")]
        public IActionResult GetProduct(int id, bool includeMaterial = false)
        {
            var product = _productRepository.GetProduct(id, includeMaterial);
            if (product == null)
            {
                return NotFound();
            }
            if (includeMaterial)
            {
                var productWithMaterialResult = Mapper.Map<ProductDto>(product);
                return Ok(productWithMaterialResult);
            }
            var onlyProductResult = Mapper.Map<ProductWithoutMaterialDto>(product);
            return Ok(onlyProductResult);
        }
复制代码

运行,查询包含Material,报错:

这是由于ProductDto里面有一个属性 ICollection<Material> Materials,automapper不知道应该怎么去映射它,因此咱们须要再添加一对Material到MaterialDto的映射关系。

复制代码
            AutoMapper.Mapper.Initialize(cfg =>
            {
                cfg.CreateMap<Product, ProductWithoutMaterialDto>();
                cfg.CreateMap<Product, ProductDto>();
                cfg.CreateMap<Material, MaterialDto>();
            });
复制代码

运行:

没问题。

而后把MaterailController里面也改一下:

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。

相关文章
相关标签/搜索