diff --git a/src/BaGet.Core.Server/Extensions/IHostExtensions.cs b/src/BaGet.Core.Server/Extensions/IHostExtensions.cs index 1314e23d..5aed4dfd 100644 --- a/src/BaGet.Core.Server/Extensions/IHostExtensions.cs +++ b/src/BaGet.Core.Server/Extensions/IHostExtensions.cs @@ -21,10 +21,7 @@ public static async Task RunMigrationsAsync(this IHost host, CancellationToken c { var ctx = scope.ServiceProvider.GetRequiredService(); - // TODO: An "InvalidOperationException" is thrown and caught due to a bug - // in EF Core 3.0. This is fixed in 3.1. - // See: https://github.com/dotnet/efcore/issues/18307 - await ctx.Database.MigrateAsync(cancellationToken); + await ctx.RunMigrationsAsync(cancellationToken); } } } diff --git a/src/BaGet.Core/Entities/AbstractContext.cs b/src/BaGet.Core/Entities/AbstractContext.cs index a2ec8d6e..ccccef12 100644 --- a/src/BaGet.Core/Entities/AbstractContext.cs +++ b/src/BaGet.Core/Entities/AbstractContext.cs @@ -1,3 +1,4 @@ +using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Metadata.Builders; @@ -31,6 +32,9 @@ public AbstractContext(DbContextOptions options) public Task SaveChangesAsync() => SaveChangesAsync(default); + public virtual async Task RunMigrationsAsync(CancellationToken cancellationToken) + => await Database.MigrateAsync(cancellationToken); + public abstract bool IsUniqueConstraintViolationException(DbUpdateException exception); public virtual bool SupportsLimitInSubqueries => true; diff --git a/src/BaGet.Core/Entities/IContext.cs b/src/BaGet.Core/Entities/IContext.cs index 118d8521..c110b59c 100644 --- a/src/BaGet.Core/Entities/IContext.cs +++ b/src/BaGet.Core/Entities/IContext.cs @@ -24,5 +24,13 @@ public interface IContext bool SupportsLimitInSubqueries { get; } Task SaveChangesAsync(CancellationToken cancellationToken); + + /// + /// Applies any pending migrations for the context to the database. + /// Creates the database if it does not already exist. + /// + /// A token to cancel the task. + /// A task that completes once migrations are applied. + Task RunMigrationsAsync(CancellationToken cancellationToken); } } diff --git a/src/BaGet.Database.PostgreSql/PostgreSqlContext.cs b/src/BaGet.Database.PostgreSql/PostgreSqlContext.cs index ad823723..c008e6cc 100644 --- a/src/BaGet.Database.PostgreSql/PostgreSqlContext.cs +++ b/src/BaGet.Database.PostgreSql/PostgreSqlContext.cs @@ -1,3 +1,5 @@ +using System.Threading; +using System.Threading.Tasks; using BaGet.Core; using Microsoft.EntityFrameworkCore; using Npgsql; @@ -24,6 +26,22 @@ public override bool IsUniqueConstraintViolationException(DbUpdateException exce code == UniqueConstraintViolationErrorCode; } + public override async Task RunMigrationsAsync(CancellationToken cancellationToken) + { + await base.RunMigrationsAsync(cancellationToken); + + // Npgsql caches the database's type information on the initial connection. + // This causes issues when BaGet creates the database as it may add the citext + // extension to support case insensitive columns. + // See: https://github.com/loic-sharma/BaGet/issues/442 + // See: https://github.com/npgsql/efcore.pg/issues/170#issuecomment-303417225 + if (Database.GetDbConnection() is NpgsqlConnection connection) + { + await connection.OpenAsync(cancellationToken); + connection.ReloadTypes(); + } + } + protected override void OnModelCreating(ModelBuilder builder) { base.OnModelCreating(builder);