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

如何在Entity Framework中避免更新关联的Game实体

问题描述

我有一个Skill实体,它与Game实体存在关联关系:

public class Skill
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Key]
    public Guid Id { get; set; }
    public virtual Game Game { get; set; }
    public virtual IdentityUser TeamMember { get; set; }
    public int SkillPoints1 { get; set; }
    public int SkillPoints2 { get; set; }
    public int SkillPoints3 { get; set; }
    public int SkillPoints4 { get; set; }
    public int SkillPoints5 { get; set; }
    public bool Spectator { get; set; }
    public Skillset SelectedSkillNumber { get; set; }
}

Game实体的定义如下:

public class Game
{
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    [Key]
    public Guid Id { get; set; }
    public string GameTitle { set; get; }
    public DateTime Created { get; set; }
    public GameState GameState { get; set; }
    public virtual Team GameTeamMembers { get; set; }
    public virtual IdentityUser GameOwner { get; set; }
    public virtual ICollection<Backlog> ProductBacklog { get; set; }
}

为了加载特定Game的所有Skill记录,我用了包含Game实体的查询:

List<Skill> allSkillsets = _context.Skills.Include(x => x.Game).Where(x => x.Game.Id == gameId).Include(x => x.TeamMember).ToList();

但在循环更新Skill的部分属性时,EF会尝试同时更新关联的Game实体:

foreach (Skill player in allSkillsets)
{
    try
    {
        player.SkillPoints1 = player.SelectedSkillNumber == Skillset.Skill1 ? 5 : boost_skillset1_by + 1;
        player.SkillPoints2 = player.SelectedSkillNumber == Skillset.Skill2 ? 5 : boost_skillset2_by + 1;
        player.SkillPoints3 = player.SelectedSkillNumber == Skillset.Skill3 ? 5 : boost_skillset3_by + 1;
        player.SkillPoints4 = player.SelectedSkillNumber == Skillset.Skill4 ? 5 : boost_skillset4_by + 1;
        player.SkillPoints5 = player.SelectedSkillNumber == Skillset.Skill5 ? 5 : boost_skillset5_by + 1;

        //_context.Entry(player.Game).State = EntityState.Detached;
        _context.Entry(player.Game).State = EntityState.Unchanged;

        _context.Skills.Attach(player);
        _context.Entry(player).Property(x => x.SkillPoints1).IsModified = true;
        _context.Entry(player).Property(x => x.SkillPoints2).IsModified = true;
        _context.Entry(player).Property(x => x.SkillPoints3).IsModified = true;
        _context.Entry(player).Property(x => x.SkillPoints4).IsModified = true;
        _context.Entry(player).Property(x => x.SkillPoints5).IsModified = true;
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        _logger.LogErrorWithObject("Could not write backlog item to database", "UpdatePlayerSkills", player);
        throw;
    }
}

由于Game实体未加载全部关联实体(比如ProductBacklog),更新操作会清除这些关联记录。我试过把Game设为null,但这会删除外键列中的Id。请问有没有办法仅更新Skill的这5个属性,而不更新关联的Game实体?

解决方案

这里有几种可行的方法来解决这个问题:

1. 查询时不加载Game实体(推荐)

根本不需要在查询Skill时包含Game实体,直接通过Skill的外键字段筛选(如果没有显式定义外键,建议先添加):

首先给Skill实体添加显式的外键属性:

public class Skill
{
    // 其他属性不变
    public Guid GameId { get; set; } // 显式外键
    public virtual Game Game { get; set; }
}

然后修改查询语句,不需要Include(x => x.Game),直接用GameId筛选:

List<Skill> allSkillsets = _context.Skills
    .Where(x => x.GameId == gameId)
    .Include(x => x.TeamMember)
    .ToList();

这样查询出来的Skill对象的Game属性是未加载的(延迟加载如果开启的话可能会触发,但这里我们不需要操作它),EF不会跟踪Game实体,更新时自然不会影响它。

2. 标记Game实体为Unchanged并简化操作

如果你必须加载Game实体,那可以在更新前明确将Game实体的状态设为Unchanged,同时注意不需要重复调用Attach(从上下文查询的实体本身已被跟踪):

foreach (Skill player in allSkillsets)
{
    try
    {
        // 更新Skill的属性
        player.SkillPoints1 = player.SelectedSkillNumber == Skillset.Skill1 ? 5 : boost_skillset1_by + 1;
        player.SkillPoints2 = player.SelectedSkillNumber == Skillset.Skill2 ? 5 : boost_skillset2_by + 1;
        player.SkillPoints3 = player.SelectedSkillNumber == Skillset.Skill3 ? 5 : boost_skillset3_by + 1;
        player.SkillPoints4 = player.SelectedSkillNumber == Skillset.Skill4 ? 5 : boost_skillset4_by + 1;
        player.SkillPoints5 = player.SelectedSkillNumber == Skillset.Skill5 ? 5 : boost_skillset5_by + 1;

        // 明确标记Game实体为Unchanged,EF不会生成它的更新语句
        _context.Entry(player.Game).State = EntityState.Unchanged;

        // 标记Skill的需要更新的属性
        _context.Entry(player).Property(x => x.SkillPoints1).IsModified = true;
        _context.Entry(player).Property(x => x.SkillPoints2).IsModified = true;
        _context.Entry(player).Property(x => x.SkillPoints3).IsModified = true;
        _context.Entry(player).Property(x => x.SkillPoints4).IsModified = true;
        _context.Entry(player).Property(x => x.SkillPoints5).IsModified = true;

        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        _logger.LogErrorWithObject("Could not write backlog item to database", "UpdatePlayerSkills", player);
        throw;
    }
}

3. 使用无跟踪查询

如果查询Skill时不需要跟踪实体状态,可以使用AsNoTracking(),然后在更新时手动附加并标记属性:

List<Skill> allSkillsets = _context.Skills
    .Include(x => x.Game)
    .Where(x => x.Game.Id == gameId)
    .Include(x => x.TeamMember)
    .AsNoTracking() // 不跟踪实体
    .ToList();

然后在更新时:

foreach (Skill player in allSkillsets)
{
    try
    {
        // 更新Skill属性
        player.SkillPoints1 = player.SelectedSkillNumber == Skillset.Skill1 ? 5 : boost_skillset1_by + 1;
        // ...其他Skill属性更新

        // 附加Skill实体
        _context.Skills.Attach(player);
        // 标记需要更新的属性
        _context.Entry(player).Property(x => x.SkillPoints1).IsModified = true;
        // ...其他属性标记

        // 标记Game为Unchanged,避免EF尝试更新它
        _context.Entry(player.Game).State = EntityState.Unchanged;

        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        _logger.LogErrorWithObject("Could not write backlog item to database", "UpdatePlayerSkills", player);
        throw;
    }
}

4. 使用批量更新(EF Core 7+)

如果使用的是EF Core 7及以上版本,可以直接用批量更新语句,不需要查询实体,效率更高:

await _context.Skills
    .Where(x => x.GameId == gameId)
    .ExecuteUpdateAsync(s => s
        .SetProperty(x => x.SkillPoints1, x => x.SelectedSkillNumber == Skillset.Skill1 ? 5 : boost_skillset1_by + 1)
        .SetProperty(x => x.SkillPoints2, x => x.SelectedSkillNumber == Skillset.Skill2 ? 5 : boost_skillset2_by + 1)
        .SetProperty(x => x.SkillPoints3, x => x.SelectedSkillNumber == Skillset.Skill3 ? 5 : boost_skillset3_by + 1)
        .SetProperty(x => x.SkillPoints4, x => x.SelectedSkillNumber == Skillset.Skill4 ? 5 : boost_skillset4_by + 1)
        .SetProperty(x => x.SkillPoints5, x => x.SelectedSkillNumber == Skillset.Skill5 ? 5 : boost_skillset5_by + 1)
    );

这种方式直接生成SQL更新语句,不会加载任何实体,自然不会影响Game实体。


内容的提问来源于stack exchange,提问作者binks

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 10:45:38