【轉載】從頭編寫 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。

相關文章
相關標籤/搜索