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

.NET 6升级至.NET 7后Npgsql驱动EF Core迁移报错问题

EF Core 7.0.0 + Npgsql迁移失败:重复键CreatedAt异常

将项目中Npgsql及相关依赖升级至7.0.0版本后,所有EF Core数据库迁移操作均无法完成,项目DbContext中包含数据种子填充逻辑。

TransportationContext上下文代码

public class TransportationContext: DbContext
{

    public DbSet<Gender> Genders { get; set; } = null!;
    public DbSet<Language> Languages { get; set; } = null!;
    public DbSet<Region> Regions { get; set; } = null!;
    public DbSet<Role> Roles { get; set; } = null!;
    public DbSet<Policy> Policies { get; set; } = null!;
    public DbSet<User> Users { get; set; } = null!;
    public DbSet<Servant> Servants { get; set; } = null!;
    public DbSet<Vehicle> Vehicles { get; set; } = null!;
    public DbSet<DiscountCode> DiscountCodes { get; set; } = null!;
    public DbSet<DiscountCodeUser> DiscountCodeUsers { get; set; } = null!;
    public DbSet<Service> Services { get; set; } = null!;
    public DbSet<ServiceBaseType> ServiceBaseTypes { get; set; } = null!;
    public DbSet<ServiceAreaType> ServiceAreaTypes { get; set; } = null!;
    public DbSet<ServiceAreaParams> ServiceAreaParams { get; set; } = null!;
    public DbSet<BaseNightPeriods> BaseNightPeriods { get; set; } = null!;
    public DbSet<ServantStatus> ServantStatuses { get; set; } = null!;
    public DbSet<Tasks> Tasks { get; set; } = null!;
    public DbSet<ServantDailyStatistic> ServantDailyStatistics { get; set; } = null!;
    public DbSet<CanceledTask> CanceledTasks { get; set; } = null!;
    public DbSet<CancelReason> CancelReasons { get; set; } = null!;
    public DbSet<CancelReasonTranslation> CancelReasonTranslations { get; set; } = null!;
    public DbSet<Request> Requests { get; set; } = null!;
    public DbSet<RequestServant> RequestServants { get; set; } = null!;
    public DbSet<Group> Groups { get; set; } = null!;
    public DbSet<ServantDailyOnlinePeriod> ServantDailyOnlinePeriods { get; set; } = null!;
    public DbSet<ServantHourlyStatistic> ServantHourlyStatistics { get; set; } = null!;
    public DbSet<TaskHourlyStatistic> TaskHourlyStatistics { get; set; } = null!;
    public DbSet<DailyStatistic> DailyStatistics { get; set; } = null!;
    public DbSet<ActiveRole> ActiveRoles { get; set; } = null!;

    [Obsolete("Obsolete")]
    static TransportationContext()
    {
        NpgsqlConnection.GlobalTypeMapper.MapEnum<DiscountCodeTypeEnum>();
        NpgsqlConnection.GlobalTypeMapper.MapEnum<TaskStatusEnum>();
        NpgsqlConnection.GlobalTypeMapper.MapEnum<RequestTypeEnum>();
        NpgsqlConnection.GlobalTypeMapper.MapEnum<RequestStatusEnum>();
    }

    public TransportationContext(DbContextOptions<TransportationContext> options) : base(options)
    {
    }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseNpgsql(o => o.UseNetTopologySuite());
        // optionsBuilder.ConfigureWarnings(w => w.Throw(RelationalEventId.MultipleCollectionIncludeWarning));
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.HasPostgresExtension("postgis");
        
        modelBuilder.HasPostgresEnum<DiscountCodeTypeEnum>();
        modelBuilder.HasPostgresEnum<TaskStatusEnum>();
        modelBuilder.HasPostgresEnum<RequestTypeEnum>();
        modelBuilder.HasPostgresEnum<RequestStatusEnum>();

        modelBuilder.Entity<Policy>().HasData(GetPolicies());
        modelBuilder.Entity<Role>().HasData(GetBaseRoles());

        modelBuilder
            .Entity<Policy>()
            .HasMany(p => p.Roles)
            .WithMany(r => r.Policies)
            .UsingEntity(j => j.HasData(GetRolePolicies(1)));

        modelBuilder.Entity<DiscountPeriods>(builder =>
        {
            // StartTime is a TimeOnly property and time on database
            builder.Property(x => x.StartTime)
                .HasConversion<TimeOnlyConverter, TimeOnlyComparer>();

            // EndTime is a TimeOnly property and time on database
            builder.Property(x => x.EndTime)
                .HasConversion<TimeOnlyConverter, TimeOnlyComparer>();
        });

        modelBuilder.Entity<ServantDailyStatistic>(builder =>
        {
            // StartTime is a TimeOnly property and time on database
            builder.Property(x => x.Date)
                .HasConversion<DateOnlyConverter, DateOnlyComparer>();
        });

        foreach (var entityType in modelBuilder.Model.GetEntityTypes())
            //If the actual entity is an auditable type. 
            if (typeof(Auditable).IsAssignableFrom(entityType.ClrType))
            {
                //This adds (In a reflection type way), a Global Query Filter
                //That always excludes deleted items. You can opt out by using dbSet.IgnoreQueryFilters()
                var parameter = Expression.Parameter(entityType.ClrType, "p");
                var deletedCheck =
                    Expression.Lambda(
                        Expression.Equal(Expression.Property(parameter, "DeletedAt"), Expression.Constant(null)),
                        parameter);
                modelBuilder.Entity(entityType.ClrType).HasQueryFilter(deletedCheck);
            }
    }

    public async Task<int> SaveChangesAsync()
    {
        AddTimestamps();
        return await base.SaveChangesAsync();
    }
}

实体修改操作

尝试给RequestServant实体添加IsOnline属性:

public class RequestServant : Auditable
{
    public int Id { get; set; }
    public bool IsOnline {get; set; }
    public int RequestId { get; set; }
    public int ServantId { get; set; }
}

迁移时抛出的异常

System.ArgumentException: An item with the same key has already been added. Key: CreatedAt
   at System.Collections.Generic.Dictionary`2.TryInsert(TKey key, TValue value, InsertionBehavior behavior)
   at System.Collections.Generic.Dictionary`2.Add(TKey key, TValue value)
   at System.Linq.Enumerable.ToDictionary[TSource,TKey](IEnumerable`1 source, Func`2 keySelector, IEqualityComparer`1 comparer)
   at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.AddSeedData(IEntityType entityType, Dictionary`2 identityMaps, EntityState initialState)
   at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.TrackData(IRelationalModel source, IRelationalModel target, DiffContext diffContext)
   at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.GetDataOperations(IRelationalModel source, IRelationalModel target, DiffContext diffContext)+MoveNext()
   at System.Linq.Enumerable.ConcatIterator`1.MoveNext()
   at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.Sort(IEnumerable`1 operations, DiffContext diffContext)
   at Microsoft.EntityFrameworkCore.Migrations.Internal.MigrationsModelDiffer.GetDifferences(IRelationalModel source, IRelationalModel target)
   at Microsoft.EntityFrameworkCore.Migrations.Design.MigrationsScaffolder.ScaffoldMigration(String migrationName, String rootNamespace, String subNamespace, String language)
   at Microsoft.EntityFrameworkCore.Design.Internal.MigrationsOperations.AddMigration(String name, String outputDir, String contextType, String namespace)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigrationImpl(String name, String outputDir, String contextType, String namespace)
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.AddMigration.<>c__DisplayClass0_0.<.ctor>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.<>c__DisplayClass3_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Design.OperationExecutor.OperationBase.Execute(Action action)
An item with the same key has already been added. Key: CreatedAt

数据种子填充及时间戳相关方法

private static IEnumerable<Policy> GetPolicies()
{
    var allPolicies = Enum.GetValues<PolicyEnum>();
    return allPolicies.Select(p => new Policy { Id = (int)p, Name = p.ToString() });
}

private static IEnumerable<Policy> GetBaseRoles()
{
    var allPolicies = Enum.GetValues<BaseRoles>();
    return allPolicies.Select(p => new Policy { Id = (int)p, Name = p.ToString() });
}

private static IEnumerable<object> GetRolePolicies(int roleId)
{
    var allPolicies = Enum.GetValues<PolicyEnum>();
    return allPolicies.Select(p => new { PoliciesId = (int)p, RolesId = roleId });
}

private void AddTimestamps()
{
    var entities = ChangeTracker.Entries()
        .Where(x => x.Entity is Auditable && (x.State == EntityState.Added || x.State == EntityState.Modified));

    foreach (var entity in entities)
    {
        var now = DateTime.UtcNow; // current datetime

        if (entity.State == EntityState.Added)
        {
            ((Auditable)entity.Entity).CreatedAt = now;
            ((Auditable)entity.Entity).UpdatedAt = now;
        }

        ((Auditable)entity.Entity).UpdatedAt = now;
    }
}

后续排查

将Microsoft.EntityFrameworkCore从7.0.0降级至6.6及相关依赖包后,迁移成功生成,推测这是EF Core 7.0.0的Bug,计划在GitHub上查找相关问题或提交新的Issue。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.12 05:31:23