EF Core异常:创建PlayerFurnitureItem时误生成CatalogItem致MySQL报错
问题描述
创建PlayerFurnitureItem实体实例时,EF Core 尝试向catalog_items表插入新记录,抛出以下异常:
MySqlConnector.MySqlException (0x80004005): Field 'stack_limit' doesn't have a default value
stack_limit是catalog_items表的字段,预期行为应为关联已有的家具项与玩家,而非创建新的CatalogItem或家具项。注释掉代码中的for循环后,程序可正常运行。
业务代码
var page = pageRepository.TryGet(eventParser.PageId); var item = page.Items.FirstOrDefault(x => x.Id == eventParser.ItemId); var newItems = new List<PlayerFurnitureItem>(); var furnitureItem = item.FurnitureItems.First(); for (var i = 0; i < eventParser.Amount; i++) { var newItem = new PlayerFurnitureItem { PlayerId = client.Player.Id, FurnitureItem = furnitureItem, LimitedData = $"1:1", CreatedAt = created }; newItems.Add(newItem); client.Player.FurnitureItems.Add(newItem); } dbContext.PlayerFurnitureItems.AddRange(newItems); await dbContext.SaveChangesAsync();
CatalogPageRepository代码
public class CatalogPageRepository(SadieContext dbContext) { private Dictionary<int, CatalogPage> _pages = []; public async Task LoadInitialDataAsync() { _pages = await dbContext.Set<CatalogPage>() .Include(x => x.Pages) .Include(x => x.Items).ThenInclude(x => x.FurnitureItems) .ToDictionaryAsync(x => x.Id, x => x); } public CatalogPage? TryGet(int pageId) { return _pages.GetValueOrDefault(pageId); } public List<CatalogPage> GetByParentId(int parentId) { return _pages .Values .Where(x => x.CatalogPageId == parentId) .ToList(); } }
问题原因与解决办法
核心原因
EF Core 将furnitureItem识别为未跟踪实体,当把它关联到新创建的PlayerFurnitureItem并调用SaveChangesAsync时,EF默认会尝试插入这个未跟踪实体(即对应的CatalogItem),触发catalog_items表的字段约束错误。
注释循环后正常,是因为单次操作时EF的跟踪逻辑未触发插入,或上下文已提前跟踪该实体;批量循环创建时,跟踪状态处理逻辑触发了自动插入。
解决办法
1. 显式附加实体到上下文
在使用furnitureItem前,将其附加到当前DbContext,告知EF这是已存在的实体,无需新增:
var furnitureItem = item.FurnitureItems.First(); // 附加实体并标记为已存在 dbContext.Attach(furnitureItem); // 后续循环代码保持不变 for (var i = 0; i < eventParser.Amount; i++) { // ... }
2. 直接设置外键而非导航属性
避免赋值FurnitureItem导航属性,直接设置对应的外键字段(假设PlayerFurnitureItem有FurnitureItemId字段):
for (var i = 0; i < eventParser.Amount; i++) { var newItem = new PlayerFurnitureItem { PlayerId = client.Player.Id, FurnitureItemId = furnitureItem.Id, // 直接绑定外键 LimitedData = $"1:1", CreatedAt = created }; newItems.Add(newItem); client.Player.FurnitureItems.Add(newItem); }
3. 确保实体被上下文跟踪
检查CatalogPageRepository加载数据时是否启用了无跟踪查询(如配置了默认AsNoTracking),若有则移除:
// 修改LoadInitialDataAsync,移除AsNoTracking(如果存在) _pages = await dbContext.Set<CatalogPage>() .Include(x => x.Pages) .Include(x => x.Items).ThenInclude(x => x.FurnitureItems) // 若之前有.AsNoTracking(),删除该行 .ToDictionaryAsync(x => x.Id, x => x);
额外检查点
- 确认
PlayerFurnitureItem与FurnitureItem的实体配置,确保HasOne/WithMany关系及外键设置正确。 - 避免在多个DbContext实例间传递实体,防止实体进入未跟踪状态。
内容的提问来源于stack exchange,提问作者user5405648
相关产品推荐
相关产品推荐

