feat: implement migration tracking and history management for PostgreSQL
This commit is contained in:
@@ -1157,18 +1157,17 @@ public sealed class BaseItemRepository
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
// UPSERT all providers using raw SQL with ON CONFLICT
|
||||
// This is atomic and handles concurrent operations correctly
|
||||
foreach (var provider in providersToUpsert)
|
||||
{
|
||||
await context.Database.ExecuteSqlAsync(
|
||||
$@"INSERT INTO library.base_item_providers (item_id, provider_id, provider_value)
|
||||
VALUES ({entity.Id}, {provider.ProviderId}, {provider.ProviderValue})
|
||||
ON CONFLICT (item_id, provider_id)
|
||||
DO UPDATE SET provider_value = EXCLUDED.provider_value",
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
// UPSERT all providers in a single UNNEST-based INSERT instead of one round-trip per provider
|
||||
var providerItemIds = providersToUpsert.Select(_ => entity.Id).ToArray();
|
||||
var providerIds = providersToUpsert.Select(p => p.ProviderId).ToArray();
|
||||
var providerValues = providersToUpsert.Select(p => p.ProviderValue).ToArray();
|
||||
await context.Database.ExecuteSqlAsync(
|
||||
$@"INSERT INTO library.base_item_providers (item_id, provider_id, provider_value)
|
||||
SELECT * FROM UNNEST({providerItemIds}, {providerIds}, {providerValues})
|
||||
ON CONFLICT (item_id, provider_id)
|
||||
DO UPDATE SET provider_value = EXCLUDED.provider_value",
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
// CRITICAL: Clear the navigation property so EF Core doesn't try to track/save these
|
||||
entity.Provider = null;
|
||||
@@ -1291,7 +1290,6 @@ public sealed class BaseItemRepository
|
||||
Value = f.Value
|
||||
}).ToArray();
|
||||
context.ItemValues.AddRange(missingItemValues);
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var itemValuesStore = existingValues.Concat(missingItemValues).ToArray();
|
||||
var valueMap = itemValueMaps
|
||||
@@ -1332,19 +1330,36 @@ public sealed class BaseItemRepository
|
||||
|
||||
await context.SaveChangesAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
// Hoist ancestor queries outside the per-item loop to avoid N×2 DB round-trips
|
||||
var ancestorItemIds = tuples
|
||||
.Where(t => t.Item.SupportsAncestors && t.AncestorIds != null)
|
||||
.Select(t => t.Item.Id)
|
||||
.ToArray();
|
||||
var allNeededAncestorIds = tuples
|
||||
.Where(t => t.Item.SupportsAncestors && t.AncestorIds != null)
|
||||
.SelectMany(t => t.AncestorIds!)
|
||||
.Distinct()
|
||||
.ToArray();
|
||||
var allExistingAncestorIds = ancestorItemIds.Length > 0
|
||||
? await context.AncestorIds
|
||||
.Where(e => ancestorItemIds.Contains(e.ItemId))
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false)
|
||||
: [];
|
||||
var validAncestorIdSet = allNeededAncestorIds.Length > 0
|
||||
? (await context.BaseItems
|
||||
.Where(e => allNeededAncestorIds.Contains(e.Id))
|
||||
.Select(f => f.Id)
|
||||
.ToArrayAsync(cancellationToken)
|
||||
.ConfigureAwait(false)).ToHashSet()
|
||||
: [];
|
||||
|
||||
foreach (var item in tuples)
|
||||
{
|
||||
if (item.Item.SupportsAncestors && item.AncestorIds != null)
|
||||
{
|
||||
var existingAncestorIds = await context.AncestorIds
|
||||
.Where(e => e.ItemId == item.Item.Id)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var validAncestorIds = await context.BaseItems
|
||||
.Where(e => item.AncestorIds.Contains(e.Id))
|
||||
.Select(f => f.Id)
|
||||
.ToArrayAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var existingAncestorIds = allExistingAncestorIds.Where(e => e.ItemId == item.Item.Id).ToList();
|
||||
var validAncestorIds = item.AncestorIds.Where(validAncestorIdSet.Contains).ToArray();
|
||||
foreach (var ancestorId in validAncestorIds)
|
||||
{
|
||||
var existingAncestorId = existingAncestorIds.FirstOrDefault(e => e.ParentItemId == ancestorId);
|
||||
|
||||
Reference in New Issue
Block a user