Fix entity half migrated entities for v2

This commit is contained in:
nicolas.dorier 2024-09-09 21:21:04 +09:00
parent 87e2f5f414
commit 222e8f66df
No known key found for this signature in database
GPG Key ID: 6618763EF09186FE
7 changed files with 64 additions and 34 deletions

View File

@ -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<ApplicationDbContext>();
builder.UseLoggerFactory(LoggerFactory);
builder.AddInterceptors(Data.InvoiceData.MigrationInterceptor.Instance);
builder.AddInterceptors(MigrationInterceptor.Instance);
ConfigureBuilder(builder, npgsqlOptionsAction);
return new ApplicationDbContext(builder.Options);
}

View File

@ -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
{
/// <summary>
/// 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.
/// </summary>
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<string> superflousProperties = new HashSet<string>()
{
"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",

View File

@ -5,6 +5,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Newtonsoft.Json.Linq;
namespace BTCPayServer.Data
{
public partial class InvoiceData : IHasBlobUntyped

View File

@ -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
{
/// <summary>
/// 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.
/// </summary>
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<InterceptionResult<int>> SavingChangesAsync(DbContextEventData eventData, InterceptionResult<int> 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<InterceptionResult<int>>(result);
}
}
}

View File

@ -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)
{

View File

@ -1,4 +1,5 @@
using System;
using System.ComponentModel.DataAnnotations.Schema;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;

View File

@ -68,14 +68,6 @@ public class InvoiceBlobMigratorHostedService : BlobMigratorHostedService<Invoic
pay.SetBlob(paymentEntity);
}
}
foreach (var entry in ctx.ChangeTracker.Entries<InvoiceData>())
{
entry.State = EntityState.Modified;
}
foreach (var entry in ctx.ChangeTracker.Entries<PaymentData>())
{
entry.State = EntityState.Modified;
}
return invoices[^1].Created;
}
}