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

如何使用EF获取数据库生成的插入实体主键?

解决EF Core中SaveChangesAsync后获取新增实体ID并记录日志的问题

当前你的代码可以正常记录删除实体和修改属性的日志,但插入实体时EntidadeId只能设为"0"——因为调用SaveChangesAsync()前无法获取数据库生成的ID;而调用之后,所有被跟踪的条目状态会变为Unchanged,无法区分哪些是刚插入的实体。

原问题代码

public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
    var entitys = ChangeTracker.Entries()
        .Where(e => (e.State == EntityState.Added || e.State == EntityState.Modified || e.State == EntityState.Deleted) &&
                    (e.Entity is EntidadeBase));    

    foreach (EntityEntry entrada in entitys.ToList())
    {
        if (entrada.State == EntityState.Added || entrada.State == EntityState.Deleted)
        {
            var log = new Log();                      

            log.UsuarioId = UsuarioId;
            log.Entidade = entrada.Entity.GetType().Name;
            log.TipoOperacao = entrada.State == EntityState.Added ? TipoOperacao.Incluir : TipoOperacao.Excluir;
            log.EntidadeId = entrada.State == EntityState.Added ? "0" : ((EntidadeBase)entrada.Entity).GetId();

            Historico.Add(log);
        }
        else
        {
            foreach (PropertyEntry prop in entrada.Properties.Where(p => p.IsModified))
            {
                var log = new Log();
                log.TipoOperacao = TipoOperacao.Alterar;
                log.Atributo = prop.Metadata.PropertyInfo?.Name;
                log.EntidadeId = ((EntidadeBase)entrada.Entity).GetId();
                log.TipoOperacao = TipoOperacao.Alterar;
                log.ValorAntigo = prop.OriginalValue?.ToString();
                log.ValorNovo = prop.CurrentValue?.ToString();

                Historico.Add(log);
            }
        }
    }

     base.SaveChangesAsync(cancellationToken);
}

解决方案:拆分日志处理流程

核心思路是先收集新增实体的引用,执行SaveChanges获取ID后再生成插入日志,同时确保删除和修改的日志在SaveChanges前完成处理(避免实体状态变化后无法访问属性)。

修改后的代码:

public override async Task<int> SaveChangesAsync(CancellationToken cancellationToken = default)
{
    // 收集待新增的实体,同时处理删除、修改的日志
    var addedEntities = new List<EntidadeBase>();
    var entityEntries = ChangeTracker.Entries()
        .Where(e => (e.State is EntityState.Added or EntityState.Modified or EntityState.Deleted) && 
                    e.Entity is EntidadeBase)
        .ToList();

    foreach (var entry in entityEntries)
    {
        if (entry.State == EntityState.Added)
        {
            // 暂存新增实体,等SaveChanges后再生成日志
            addedEntities.Add((EntidadeBase)entry.Entity);
        }
        else if (entry.State == EntityState.Deleted)
        {
            // 删除日志必须在SaveChanges前处理,否则实体会被分离无法访问
            var log = new Log
            {
                UsuarioId = UsuarioId,
                Entidade = entry.Entity.GetType().Name,
                TipoOperacao = TipoOperacao.Excluir,
                EntidadeId = ((EntidadeBase)entry.Entity).GetId()
            };
            Historico.Add(log);
        }
        else if (entry.State == EntityState.Modified)
        {
            // 修改日志可提前处理,实体ID已存在,属性变更值也能正常获取
            foreach (var prop in entry.Properties.Where(p => p.IsModified))
            {
                var log = new Log
                {
                    TipoOperacao = TipoOperacao.Alterar,
                    Atributo = prop.Metadata.PropertyInfo?.Name,
                    EntidadeId = ((EntidadeBase)entry.Entity).GetId(),
                    ValorAntigo = prop.OriginalValue?.ToString(),
                    ValorNovo = prop.CurrentValue?.ToString()
                };
                Historico.Add(log);
            }
        }
    }

    // 执行SaveChanges,此时EF会自动将数据库生成的ID填充到新增实体中
    var result = await base.SaveChangesAsync(cancellationToken);

    // 生成新增实体的日志,此时能获取到正确的ID
    foreach (var entity in addedEntities)
    {
        var log = new Log
        {
            UsuarioId = UsuarioId,
            Entidade = entity.GetType().Name,
            TipoOperacao = TipoOperacao.Incluir,
            EntidadeId = entity.GetId()
        };
        Historico.Add(log);
    }

    // 保存新增实体的日志(如果Historico是DbSet,需再次调用SaveChanges)
    if (addedEntities.Any())
    {
        await base.SaveChangesAsync(cancellationToken);
    }

    return result;
}

关键说明

  1. 新增实体处理:先暂存实体引用,不提前生成日志,等SaveChanges后实体的主键ID被EF填充,再调用GetId()获取正确值。
  2. 删除实体处理:必须在SaveChanges前生成日志,因为执行删除后实体状态会变为Detached,无法再访问其属性。
  3. 修改实体处理:可提前生成日志,因为修改的实体已存在于数据库中,ID和属性变更值都能正常读取。
  4. 日志保存:如果Historico是当前DbContext的DbSet<Log>,新增的日志条目会被上下文跟踪,需要再次调用SaveChanges完成日志的持久化。

可选优化(事务一致性)

如果需要保证实体变更和日志的原子性,可以用EF的事务包裹两次SaveChanges:

using (var transaction = await Database.BeginTransactionAsync(cancellationToken))
{
    try
    {
        // 执行实体变更和删除、修改日志的保存
        var result = await base.SaveChangesAsync(cancellationToken);

        // 生成并保存新增实体的日志
        foreach (var entity in addedEntities)
        {
            var log = new Log
            {
                UsuarioId = UsuarioId,
                Entidade = entity.GetType().Name,
                TipoOperacao = TipoOperacao.Incluir,
                EntidadeId = entity.GetId()
            };
            Historico.Add(log);
        }
        await base.SaveChangesAsync(cancellationToken);

        await transaction.CommitAsync(cancellationToken);
        return result;
    }
    catch
    {
        await transaction.RollbackAsync(cancellationToken);
        throw;
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 15:40:17