问题描述
我正在尝试使用 Postman 将此 Json 发送到我的 API:
{
"name": "yummy food","brand": "brand","tags": [
"1","2"
]
}
但我收到此错误:
{
"type": "https://tools.ietf.org/html/rfc7231#section-6.5.1","title": "One or more validation errors occurred.","status": 400,"traceId": "00-b7a6042817b5124294f5c6d2f6169f05-70d797d0744bfe40-00","errors": {
"$.tags[0]": [
"The JSON value Could not be converted to GroceryItemTag. Path: $.tags[0] | LineNumber: 4 | BytePositionInLine: 11."
]
}
}
帖子中的 GroceryItemTag 字段(“标签”)是一个枚举,但使用查找表作为 GroceryItemTag
对象,具有枚举 ID、名称和 iconCodePoint 字段。
邮递员请求:
这是我的实体框架核心模型:
杂货:
using System.Collections.Generic;
namespace Vepo.Domain
{
public class GroceryItem : VeganItem<GroceryItem,GroceryItemTagEnum,GroceryItemTag>
{
public string Name {get; set;}
public string Brand {get; set;}
public string Description {get; set;}
public string Image {get; set;}
public virtual ICollection<GroceryItemGroceryStore> GroceryItemGroceryStores { get; set; }
}
}
GroceryItem 的基类(注意虚拟 Tags
和 TagIds
):
using System.Collections.Generic;
namespace Vepo.Domain
{
public abstract class VeganItem<VeganItemType,VeganItemTagEnumType,VeganItemTagType>
{
public int Id { get; set; }
public int IsNotVeganCount { get; set; }
public int IsveganCount { get; set; }
public int ratingsCount { get; set; }
public int rating { get; set; }
public List<VeganItemTagEnumType> TagIds { get; set; }
public virtual List<VeganItemTagType> Tags { get; set; }
public List<Establishment<VeganItemType>> Establishments { get; set; }
public int CurrentRevisionId { get; set; }
}
}
GroceryItemTagEnum:
public enum GroceryItemTagEnum
{
BabyAndChild = 1,Baking,Bathroom,BeerAndWine,Condiments,Confectionary,Cooking,Dessert,Drinks,FauxDairy,FauxMeat,FauxSeafood,FridgeAndDeli,Frozen,HealthFood,HouseHold,Other,Pantry,Pet,}
用于查找表的 GroceryItemTag 类:
public class GroceryItemTag
{
public GroceryItemTagEnum Id { get; set; }
public int IconCodePoint {get; set;}
public string Name { get; set; }
}
控制器,注意PostGroceryItem
:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using Vepo.Domain;
namespace Vepo.Controllers
{
[Route("api/[controller]")]
[ApiController]
public class GroceryItemsController : ControllerBase
{
private readonly VepoContext _context;
public GroceryItemsController(VepoContext context)
{
_context = context;
}
// GET: api/GroceryItems
[HttpGet]
public async Task<ActionResult<IEnumerable<GroceryItem>>> GetGroceryItems()
{
return await _context.GroceryItems.ToListAsync();
}
// GET: api/GroceryItems/5
[HttpGet("{id}")]
public async Task<ActionResult<GroceryItem>> GetGroceryItem(int id)
{
var groceryItem = await _context.GroceryItems.FindAsync(id);
if (groceryItem == null)
{
return NotFound();
}
return groceryItem;
}
// PUT: api/GroceryItems/5
// To protect from overposting attacks,see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPut("{id}")]
public async Task<IActionResult> PutGroceryItem(int id,GroceryItem groceryItem)
{
if (id != groceryItem.Id)
{
return BadRequest();
}
_context.Entry(groceryItem).State = EntityState.Modified;
try
{
await _context.SaveChangesAsync();
}
catch (dbupdateConcurrencyException)
{
if (!GroceryItemExists(id))
{
return NotFound();
}
else
{
throw;
}
}
return NoContent();
}
// POST: api/GroceryItems
// To protect from overposting attacks,see https://go.microsoft.com/fwlink/?linkid=2123754
[HttpPost]
public async Task<ActionResult<GroceryItem>> PostGroceryItem(GroceryItem groceryItem)
{
_context.GroceryItems.Add(groceryItem);
await _context.SaveChangesAsync();
return CreatedAtAction("GetGroceryItem",new { id = groceryItem.Id },groceryItem);
}
// DELETE: api/GroceryItems/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteGroceryItem(int id)
{
var groceryItem = await _context.GroceryItems.FindAsync(id);
if (groceryItem == null)
{
return NotFound();
}
_context.GroceryItems.Remove(groceryItem);
await _context.SaveChangesAsync();
return NoContent();
}
private bool GroceryItemExists(int id)
{
return _context.GroceryItems.Any(e => e.Id == id);
}
}
}
我的数据库上下文为 GroceryItemTag 查找表提供种子:
using Microsoft.EntityFrameworkCore;
namespace Vepo.Domain
{
public class VepoContext : DbContext
{
public VepoContext(DbContextOptions<VepoContext> options)
: base(options)
{
}
protected override void OnModelCreating(ModelBuilder builder)
{
builder.Entity<GroceryItemGroceryStore>().HasKey(table => new
{
table.GroceryItemId,table.GroceryStoreId
});
builder.Entity<MenuItemRestaurant>().HasKey(table => new
{
table.MenuItemId,table.RestaurantId
});
builder.Entity<GroceryItemTag>()
.Property(tag => tag.Id)
.ValueGeneratednever();
builder.Entity<MenuItemTag>()
.Property(tag => tag.Id)
.ValueGeneratednever();
builder.Entity<GroceryItemTag>().HasData(
new GroceryItemTag[] {
new GroceryItemTag {
Name = "Baby & Child",Id = GroceryItemTagEnum.BabyAndChild,IconCodePoint = 0xf77c
},new GroceryItemTag {
Name = "Baking",Id = GroceryItemTagEnum.Baking,IconCodePoint = 0xf563
},new GroceryItemTag {
Name = "Beer & Wine",Id = GroceryItemTagEnum.BeerAndWine,IconCodePoint = 0xf4e3
},new GroceryItemTag {
Name = "Condiments",Id = GroceryItemTagEnum.Condiments,IconCodePoint = 0xf72f
},new GroceryItemTag {
Name = "Confectionary",Id = GroceryItemTagEnum.Confectionary,IconCodePoint = 0xf819
},new GroceryItemTag {
Name = "Cooking",Id = GroceryItemTagEnum.Cooking,IconCodePoint = 0xe01d
},new GroceryItemTag {
Name = "Dessert",Id = GroceryItemTagEnum.Dessert,IconCodePoint = 0xf810
},new GroceryItemTag {
Name = "Drinks",Id = GroceryItemTagEnum.Drinks,IconCodePoint = 0xf804
},new GroceryItemTag {
Name = "Faux Meat",Id = GroceryItemTagEnum.FauxMeat,IconCodePoint = 0xf814
},new GroceryItemTag {
Name = "Faux Dairy",Id = GroceryItemTagEnum.FauxDairy,IconCodePoint = 0xf7f0
},new GroceryItemTag {
Name = "Faux Seafood",Id = GroceryItemTagEnum.FauxSeafood,IconCodePoint = 0xf7fe
},new GroceryItemTag {
Name = "Fridge & Deli",Id = GroceryItemTagEnum.FridgeAndDeli,IconCodePoint = 0xe026
},new GroceryItemTag {
Name = "Frozen",Id = GroceryItemTagEnum.Frozen,IconCodePoint = 0xf7ad
},new GroceryItemTag {
Name = "Bathroom",Id = GroceryItemTagEnum.Bathroom,IconCodePoint = 0xe06b
},new GroceryItemTag {
Name = "Health Food",Id = GroceryItemTagEnum.HealthFood,IconCodePoint = 0xf787
},new GroceryItemTag {
Name = "Household",Id = GroceryItemTagEnum.HouseHold,IconCodePoint = 0xf898
},new GroceryItemTag {
Name = "Pantry",Id = GroceryItemTagEnum.Pantry,IconCodePoint = 0xf7eb
},new GroceryItemTag {
Name = "Pet",Id = GroceryItemTagEnum.Pet,IconCodePoint = 0xf6d3
},new GroceryItemTag {
Name = "Other",Id = GroceryItemTagEnum.Other,IconCodePoint = 0xf39b
}});
builder.Entity<GroceryItem>()
.Property(e => e.Tags)
.HasConversion(
v => JsonSerializer.Serialize(v,null),v => JsonSerializer.Deserialize<List<GroceryItemTag>>(v,new ValueComparer<IList<GroceryItemTag>>(
(c1,c2) => c1.SequenceEqual(c2),c => c.Aggregate(0,(a,v) => HashCode.Combine(a,v.GetHashCode())),c => (IList<GroceryItemTag>)c.ToList()));
}
public DbSet<GroceryItem> GroceryItems { get; set; }
public DbSet<GroceryItemGroceryStore> GroceryItemGroceryStores { get; set; }
public DbSet<MenuItemRestaurant> MenuItemRestaurants { get; set; }
public DbSet<MenuItem> MenuItems { get; set; }
public DbSet<GroceryStore> GroceryStores { get; set; }
public DbSet<GroceryItemTag> GroceryItemTags { get; set; }
public DbSet<MenuItemTag> MenuItemTags { get; set; }
public DbSet<Restaurant> Restaurants { get; set; }
}
}
我如何从邮递员那里发送能够将标签 ID 转换为 GroceryItemTag
的帖子
我知道我需要这样做 (See official Microsoft docs):
builder.Entity<GroceryItem>()
.Property(e => e.Tags)
.HasConversion(
v => JsonSerializer.Serialize(v,new ValueComparer<IList<GroceryItemTag>>(
(c1,c => (IList<GroceryItemTag>)c.ToList()));
我只是无法让它工作,例如,它可以编译,但我得到了完全相同的错误。完全没有变化。
解决方法
我相信我误解了 ValueConverter/ValueComparer 来存储整个对象,而不仅仅是一个枚举 ID。我认为发送的 JSON 需要具有完整的对象值,而不仅仅是枚举 (id)。我认为 ValueConverter/ValueComparer
会使查找表过时。
我删除了 VeganItem.TagIds
,使 VeganItem.Tags
不是虚拟的,并将 json 负载更改为:
{
"name": "yummy food","brand": "brand","tags": [
{"name":"Baking","id":2,"iconCodePoint": 23145}
]
}
它奏效了。我相信这是应该做的,因为我也在此处注释掉代码的同时尝试了它:
builder.Entity<GroceryItem>()
.Property(e => e.Tags)
.HasConversion(
v => JsonSerializer.Serialize(v,null),v => JsonSerializer.Deserialize<List<GroceryItemTag>>(v,new ValueComparer<IList<GroceryItemTag>>(
(c1,c2) => c1.SequenceEqual(c2),c => c.Aggregate(0,(a,v) => HashCode.Combine(a,v.GetHashCode())),c => (IList<GroceryItemTag>)c.ToList()));
当上面的代码被注释掉时它没有成功地转换 JSON,但是当它被取消注释时却成功了,这让我相信 ValueComparer
和 ValueConverter
正在按预期工作通过 Entity Framework Core。
这会导致标签数据库列存储这个序列化值: