diff --git a/BTCPayServer.Data/ApplicationDbContextFactory.cs b/BTCPayServer.Data/ApplicationDbContextFactory.cs index d9ba9ce3a..feeb31209 100644 --- a/BTCPayServer.Data/ApplicationDbContextFactory.cs +++ b/BTCPayServer.Data/ApplicationDbContextFactory.cs @@ -1,6 +1,4 @@ using System; -using System.Runtime.CompilerServices; -using System.Threading.Tasks; using BTCPayServer.Abstractions.Contracts; using BTCPayServer.Abstractions.Models; using Microsoft.EntityFrameworkCore; @@ -23,7 +21,7 @@ namespace BTCPayServer.Data { var builder = new DbContextOptionsBuilder(); builder.UseLoggerFactory(LoggerFactory); - builder.AddInterceptors(Data.InvoiceData.MigrationInterceptor.Instance); + builder.AddInterceptors(MigrationInterceptor.Instance); ConfigureBuilder(builder, npgsqlOptionsAction); return new ApplicationDbContext(builder.Options); } diff --git a/BTCPayServer.Data/Data/InvoiceData.Migration.cs b/BTCPayServer.Data/Data/InvoiceData.Migration.cs index 80e1d8f90..cd876c48d 100644 --- a/BTCPayServer.Data/Data/InvoiceData.Migration.cs +++ b/BTCPayServer.Data/Data/InvoiceData.Migration.cs @@ -15,32 +15,13 @@ using Microsoft.EntityFrameworkCore.Diagnostics; using BTCPayServer.Migrations; using Newtonsoft.Json.Serialization; using Microsoft.AspNetCore.Mvc.ModelBinding.Binders; +using System.Threading.Tasks; +using System.Threading; namespace BTCPayServer.Data { - public partial class InvoiceData + public partial class InvoiceData : MigrationInterceptor.IHasMigration { - /// - /// We have a migration running in the background that will migrate the data from the old blob to the new blob - /// Meanwhile, we need to make sure that invoices which haven't been migrated yet are migrated on the fly. - /// - public class MigrationInterceptor : IMaterializationInterceptor - { - public static readonly MigrationInterceptor Instance = new MigrationInterceptor(); - public object InitializedInstance(MaterializationInterceptionData materializationData, object entity) - { - if (entity is InvoiceData invoiceData && invoiceData.Currency is null) - { - invoiceData.Migrate(); - } - else if (entity is PaymentData paymentData && paymentData.Currency is null) - { - paymentData.Migrate(); - } - return entity; - } - } - static HashSet superflousProperties = new HashSet() { "availableAddressHashes", @@ -369,6 +350,11 @@ namespace BTCPayServer.Data blob["version"] = 3; Blob2 = blob.ToString(Formatting.None); } + + public bool ShouldMigrate() => Currency is null; + + [NotMapped] + public bool Migrated { get; set; } static string[] detailsRemoveDefault = [ "paymentMethodFeeRate", diff --git a/BTCPayServer.Data/Data/InvoiceData.cs b/BTCPayServer.Data/Data/InvoiceData.cs index 0196c2fbd..29c46286e 100644 --- a/BTCPayServer.Data/Data/InvoiceData.cs +++ b/BTCPayServer.Data/Data/InvoiceData.cs @@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; using Newtonsoft.Json.Linq; + namespace BTCPayServer.Data { public partial class InvoiceData : IHasBlobUntyped diff --git a/BTCPayServer.Data/Data/MigrationInterceptor.cs b/BTCPayServer.Data/Data/MigrationInterceptor.cs new file mode 100644 index 000000000..7a824896b --- /dev/null +++ b/BTCPayServer.Data/Data/MigrationInterceptor.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore.Diagnostics; +using Microsoft.EntityFrameworkCore; + +namespace BTCPayServer.Data +{ + /// + /// We have a migration running in the background that will migrate the data from the old blob to the new blob + /// Meanwhile, we need to make sure that invoices which haven't been migrated yet are migrated on the fly. + /// + public class MigrationInterceptor : IMaterializationInterceptor, ISaveChangesInterceptor + { + public interface IHasMigration + { + bool ShouldMigrate(); + void Migrate(); + bool Migrated { get; set; } + } + public static readonly MigrationInterceptor Instance = new MigrationInterceptor(); + public object InitializedInstance(MaterializationInterceptionData materializationData, object entity) + { + if (entity is IHasMigration hasMigration && hasMigration.ShouldMigrate()) + { + hasMigration.Migrate(); + hasMigration.Migrated = true; + } + return entity; + } + + public ValueTask> SavingChangesAsync(DbContextEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default(CancellationToken)) + { + + foreach (var entry in eventData.Context.ChangeTracker.Entries()) + { + if (entry is { Entity: IHasMigration { Migrated: true }, State: EntityState.Modified }) + // It seems doing nothing, but this actually set all properties as modified + entry.State = EntityState.Modified; + } + return new ValueTask>(result); + } + } +} diff --git a/BTCPayServer.Data/Data/PaymentData.Migration.cs b/BTCPayServer.Data/Data/PaymentData.Migration.cs index 207332083..b72742642 100644 --- a/BTCPayServer.Data/Data/PaymentData.Migration.cs +++ b/BTCPayServer.Data/Data/PaymentData.Migration.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using System.Reflection.Metadata; using System.Text; @@ -13,7 +14,7 @@ using Newtonsoft.Json.Linq; namespace BTCPayServer.Data { - public partial class PaymentData + public partial class PaymentData : MigrationInterceptor.IHasMigration { public void Migrate() { @@ -159,6 +160,10 @@ namespace BTCPayServer.Data #pragma warning restore CS0618 // Type or member is obsolete } + public bool ShouldMigrate() => Currency is null; + [NotMapped] + public bool Migrated { get; set; } + static readonly DateTimeOffset unixRef = new DateTimeOffset(1970, 1, 1, 0, 0, 0, TimeSpan.Zero); public static long DateTimeToMilliUnixTime(in DateTime time) { diff --git a/BTCPayServer.Data/Data/PaymentData.cs b/BTCPayServer.Data/Data/PaymentData.cs index 52233b417..f2872d970 100644 --- a/BTCPayServer.Data/Data/PaymentData.cs +++ b/BTCPayServer.Data/Data/PaymentData.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel.DataAnnotations.Schema; using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore.Infrastructure; diff --git a/BTCPayServer/HostedServices/InvoiceBlobMigratorHostedService.cs b/BTCPayServer/HostedServices/InvoiceBlobMigratorHostedService.cs index 1833d3522..2b4abfc78 100644 --- a/BTCPayServer/HostedServices/InvoiceBlobMigratorHostedService.cs +++ b/BTCPayServer/HostedServices/InvoiceBlobMigratorHostedService.cs @@ -68,14 +68,6 @@ public class InvoiceBlobMigratorHostedService : BlobMigratorHostedService()) - { - entry.State = EntityState.Modified; - } - foreach (var entry in ctx.ChangeTracker.Entries()) - { - entry.State = EntityState.Modified; - } return invoices[^1].Created; } }