bilder
All checks were successful
Branch - test - Build and Push Backend API Docker Image / build-and-push (push) Successful in 26s

This commit is contained in:
Tizian.Breuch
2025-11-25 10:55:53 +01:00
parent 6dda8cd106
commit d5dad225b5

View File

@@ -114,7 +114,9 @@ namespace Webshop.Application.Services.Admin
public async Task<ServiceResult> UpdateAdminProductAsync(UpdateAdminProductDto productDto)
{
// 1. Produkt laden (Tracked)
Console.WriteLine($"---- UPDATE START: Produkt-ID {productDto.Id} ----");
// 1. Produkt aus der DB laden (inklusive aktueller RowVersion)
var existingProduct = await _context.Products
.Include(p => p.Images)
.Include(p => p.Productcategories)
@@ -125,23 +127,32 @@ namespace Webshop.Application.Services.Admin
return ServiceResult.Fail(ServiceResultType.NotFound, $"Produkt mit ID '{productDto.Id}' nicht gefunden.");
}
// 2. Concurrency Check (nur initial)
// 2. ROBUSTER CONCURRENCY CHECK (Manuell)
// Wir vergleichen die Strings direkt. Das ist sicherer als Byte-Manipulation.
if (!string.IsNullOrEmpty(productDto.RowVersion))
{
try
// DB-Wert in String wandeln
string dbRowVersion = Convert.ToBase64String(existingProduct.RowVersion ?? new byte[0]);
// Frontend-Wert bereinigen (Leerzeichen zu Plus, falls URL-Decoded wurde)
string incomingRowVersion = productDto.RowVersion.Trim().Replace(" ", "+");
Console.WriteLine($"DB Version: '{dbRowVersion}'");
Console.WriteLine($"Frontend Version: '{incomingRowVersion}'");
if (dbRowVersion != incomingRowVersion)
{
string fixedRowVersion = productDto.RowVersion.Replace(" ", "+");
byte[] incomingRowVersion = Convert.FromBase64String(fixedRowVersion);
_context.Entry(existingProduct).Property(p => p.RowVersion).OriginalValue = incomingRowVersion;
}
catch (FormatException)
{
return ServiceResult.Fail(ServiceResultType.Failure, "RowVersion Format ist ung<6E>ltig.");
Console.WriteLine("!!! KONFLIKT: Versionen stimmen nicht <20>berein !!!");
return ServiceResult.Fail(ServiceResultType.Conflict, "Das Produkt wurde in der Zwischenzeit von jemand anderem bearbeitet. Bitte laden Sie die Seite neu.");
}
}
// Wenn wir hier sind, ist die Version KORREKT.
// Wir m<>ssen OriginalValue NICHT mehr setzen, da existingProduct ja
// die aktuelle Version aus der DB hat.
// -----------------------------------------------------------------------
// SCHRITT A: BILDER & KATEGORIEN UPDATE
// SCHRITT A: BILDER UPDATE
// -----------------------------------------------------------------------
bool imagesChanged = false;
@@ -156,7 +167,7 @@ namespace Webshop.Application.Services.Admin
}
}
// Hauptbild
// Hauptbild ersetzen
if (productDto.MainImageFile != null)
{
var existingMainImage = existingProduct.Images.FirstOrDefault(img => img.IsMainImage);
@@ -168,7 +179,7 @@ namespace Webshop.Application.Services.Admin
imagesChanged = true;
}
// Zusatzbilder
// Zusatzbilder hinzuf<75>gen
if (productDto.AdditionalImageFiles != null && productDto.AdditionalImageFiles.Any())
{
int displayOrder = (existingProduct.Images.Any() ? existingProduct.Images.Max(i => i.DisplayOrder) : 0) + 1;
@@ -181,42 +192,63 @@ namespace Webshop.Application.Services.Admin
imagesChanged = true;
}
// Kategorien
existingProduct.Productcategories.Clear();
// Kategorien aktualisieren
if (productDto.CategorieIds != null)
{
foreach (var categorieId in productDto.CategorieIds)
// Bestehende IDs
var currentIds = existingProduct.Productcategories.Select(pc => pc.categorieId).ToList();
// Neue IDs
var newIds = productDto.CategorieIds;
// Nur <20>ndern, wenn wirklich anders (vermeidet unn<6E>tige DB-Writes)
if (!new HashSet<Guid>(currentIds).SetEquals(newIds))
{
existingProduct.Productcategories.Clear();
foreach (var categorieId in newIds)
{
existingProduct.Productcategories.Add(new Productcategorie { categorieId = categorieId });
}
}
}
// WICHTIG: Wenn Bilder ge<67>ndert wurden, speichern wir HIER schon einmal zwischen.
// Das erlaubt der DB, Trigger auszuf<75>hren und die RowVersion zu aktualisieren.
// SCHRITT A SPEICHERN (Nur wenn Bilder ge<67>ndert wurden)
if (imagesChanged)
{
// Wir aktualisieren den Zeitstempel manuell, damit der User Feedback hat
existingProduct.LastModifiedDate = DateTimeOffset.UtcNow;
existingProduct.RowVersion = Guid.NewGuid().ToByteArray();
try
{
await _context.SaveChangesAsync();
// Ganz wichtig: Die RowVersion im Entity neu laden, da sie sich in der DB ge<67>ndert hat!
// Aber: Da wir das Objekt weiterverwenden wollen, m<>ssen wir sicherstellen,
// dass EF Core wei<65>, dass die Version jetzt "neu" ist.
// Der einfachste Weg: Wir holen uns das aktuelle Token aus der DB.
await _context.Entry(existingProduct).ReloadAsync();
// Da wir gespeichert haben, hat das Entity jetzt eine NEUE RowVersion in der DB.
// Aber unser EF-Kontext wei<65> das bereits, weil das Objekt getracked ist.
}
catch (DbUpdateConcurrencyException)
catch (Exception ex)
{
return ServiceResult.Fail(ServiceResultType.Conflict, "Konflikt beim Aktualisieren der Bilder.");
Console.WriteLine($"Fehler beim Bild-Speichern: {ex.Message}");
return ServiceResult.Fail(ServiceResultType.Failure, "Fehler beim Speichern der Bilder.");
}
}
// -----------------------------------------------------------------------
// SCHRITT B: PRODUKT EIGENSCHAFTEN UPDATE
// SCHRITT B: PRODUKT EIGENSCHAFTEN
// -----------------------------------------------------------------------
// Check auf Duplikate (SKU/Slug) hier wie gehabt...
// Check auf Duplikate (SKU/Slug) - nur wenn ge<67>ndert
if (existingProduct.SKU != productDto.SKU)
{
bool skuExists = await _context.Products.AnyAsync(p => p.SKU == productDto.SKU && p.Id != productDto.Id);
if (skuExists) return ServiceResult.Fail(ServiceResultType.Conflict, $"SKU '{productDto.SKU}' existiert bereits.");
}
if (existingProduct.Slug != productDto.Slug)
{
bool slugExists = await _context.Products.AnyAsync(p => p.Slug == productDto.Slug && p.Id != productDto.Id);
if (slugExists) return ServiceResult.Fail(ServiceResultType.Conflict, $"Slug '{productDto.Slug}' existiert bereits.");
}
// Werte <20>bernehmen
existingProduct.Name = productDto.Name;
existingProduct.Description = productDto.Description;
existingProduct.SKU = productDto.SKU;
@@ -231,17 +263,20 @@ namespace Webshop.Application.Services.Admin
existingProduct.IsFeatured = productDto.IsFeatured;
existingProduct.FeaturedDisplayOrder = productDto.FeaturedDisplayOrder;
// Immer aktualisieren
existingProduct.LastModifiedDate = DateTimeOffset.UtcNow;
// Neue Version setzen
existingProduct.RowVersion = Guid.NewGuid().ToByteArray();
try
{
Console.WriteLine("---- RUFE SaveChangesAsync (Properties) AUF ----");
await _context.SaveChangesAsync();
}
catch (DbUpdateConcurrencyException)
{
return ServiceResult.Fail(ServiceResultType.Conflict, "Das Produkt wurde bearbeitet. Bitte neu laden.");
// Sollte dank des manuellen Checks oben kaum noch passieren,
// es sei denn, millisekundengenaue Race-Condition.
return ServiceResult.Fail(ServiceResultType.Conflict, "Konflikt beim Speichern der Daten.");
}
return ServiceResult.Ok();