You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 12:05:35