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

EF Core加载未修改实体保存触发主键约束冲突问题排查

问题描述

我正在开发一个使用Entity Framework Core将数据持久化到Microsoft SQL Server的Azure Function应用。当前遇到的问题是:在服务代码中加载活跃的NotificationRecord记录,未做任何修改就将其添加到上下文并保存,却触发了主键约束冲突错误,提示无法插入重复主键值。我搞不懂为什么Entity Framework Core会把这些已存在的实体当成新实体来尝试插入,附上相关代码,求帮忙排查原因。

错误信息

Microsoft.EntityFrameworkCore.DbUpdateException: An error occurred while saving the entity changes. See the inner exception for details.
Microsoft.Data.SqlClient.SqlException (0x80131904): Violation of PRIMARY KEY constraint 'PK_NotificationRecords'. Cannot insert duplicate key in object 'dbo.NotificationRecords'. The duplicate key value is (02289e67-e7f1-401e-f603-08dcf4ef7c1b).

Service代码

private async Task DispatchNotifications(List<ApplicationsByOwner> applicationsByOwners, CancellationToken cancellationToken)
{
    var activeNotificationRecords = (await notificationRepository.GetActiveNotificationRecords(assetType: AssetType.Application, cancellationToken: cancellationToken)).ToList();
    await notificationRepository.AddNotificationRecordsAsync(activeNotificationRecords, cancellationToken);
    await notificationRepository.SaveChangesAsync(cancellationToken); // ERROR HERE
}

DbContext代码

public sealed class AssetOwnershipContext : DbContext
{
    public DbSet<NotificationRecord> NotificationRecords { get; set; }

    public AssetOwnershipContext(DbContextOptions<AssetOwnershipContext> options) : base(options)
    {
        Database.EnsureCreated();
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        // NotificationRecord
        modelBuilder.Entity<NotificationRecord>().Property(p => p.CreatedAt).HasDefaultValueSql("GETUTCDATE()");
        modelBuilder.Entity<NotificationRecord>().Property(p => p.UpdatedAt).HasDefaultValueSql("GETUTCDATE()").ValueGeneratedOnAdd();
        modelBuilder.Entity<NotificationRecord>().Property(d => d.AssetType).HasConversion<string>();

        base.OnModelCreating(modelBuilder);
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        base.OnConfiguring(optionsBuilder);
    }
}

Repository代码

public async Task<IEnumerable<NotificationRecord>> GetActiveNotificationRecords(Guid? owner = default, AssetType? assetType = default, CancellationToken cancellationToken = default) 
{
   var query = context.NotificationRecords.Where(x => x.IsActive);

   if (owner.HasValue)
   {
       query = query.Where(x => x.OwnerId == owner);
   }

   if (assetType.HasValue)
   {
       query = query.Where(x => x.AssetType == assetType);
   }

   return await query.ToListAsync(cancellationToken);
}

public async Task AddNotificationRecordsAsync(IEnumerable<NotificationRecord> notificationRecords, CancellationToken cancellationToken = default)
{
    await context.NotificationRecords.AddRangeAsync(notificationRecords, cancellationToken);
}

public async Task<bool> SaveChangesAsync(CancellationToken cancellationToken = default)
{
    return await context.SaveChangesAsync(cancellationToken) >= 0;
}

NotificationRecord实体代码

[Index(nameof(OwnerId))]
[Index(nameof(AssetId))]
[Index(nameof(UserPrincipalName))]
[Index(nameof(CreatedAt))]
public class NotificationRecord
{
    [Key]
    [DatabaseGenerated(DatabaseGeneratedOption.Identity)]
    public Guid Id { get; init; }
    [Required] public Guid OwnerId { get; set; }
    [Required] public Guid AssetId { get; set; }
    [Required] public AssetType AssetType { get; set; }
    [MaxLength(256)] public string? DisplayName { get; set; }
    [MaxLength(256)] public string? GivenName { get; set; }
    [MaxLength(256)] public string? Surname { get; set; }
    [Required][MaxLength(256)] public string? UserPrincipalName { get; set; }
    // [Required] public ICollection<Notification> Notifications { get; } = new List<Notification>();
    [Required] public DateTime? CreatedAt { get; set; }
    [Required] public DateTime? UpdatedAt { get; set; }
    [Required] public bool IsActive { get; set; } = true;
}
问题原因与解决方法

核心原因

调用AddRangeAsync方法处理已存在的实体时,EF Core会将这些实体标记为Added状态,尝试插入新记录。而这些实体本身已经在数据库中存在,主键重复直接导致约束冲突。

额外可能的影响因素:

  • 如果DbContext的生命周期管理不当(比如每次操作都新建上下文),加载的实体可能处于Detached状态,EF Core无法识别它们是已存在的记录。
  • Id属性使用init修饰符不影响状态跟踪,但如果上下文无法关联到已加载的实体,依然会被视为新实体。

解决方法

  1. 移除多余的Add操作:你的代码中没有修改实体,完全不需要调用AddNotificationRecordsAsync,直接删除这一行即可,调用SaveChangesAsync不会触发任何不必要的插入操作。

  2. 关联Detached实体(如果需要):如果确实需要将处于Detached状态的实体重新关联到上下文(比如跨上下文操作),用AttachRange替代AddRangeAsync,它会将实体标记为Unchanged状态,EF Core不会尝试插入,只会跟踪后续修改:

public void AttachNotificationRecords(IEnumerable<NotificationRecord> notificationRecords)
{
    context.NotificationRecords.AttachRange(notificationRecords);
}

之后在Service中按需调用该方法即可。

  1. 检查上下文生命周期:在Azure Function中确保DbContext使用Scoped生命周期,避免频繁创建新上下文导致实体状态丢失。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 20:55:23