feat: Implementa gestione intelligente della chiave sorgente con rilevamento PK
- Aggiunge rilevamento automatico Primary Key per connessioni database - Rimuove completamente il fallback automatico per lato sorgente - Implementa selezione manuale obbligatoria per file e sorgenti non-DB - Migliora UI con suggerimenti intelligenti e feedback visivo - Aggiunge validazione multi-livello (UI, pre-transfer, runtime) - Introduce metodo GetPrimaryKeyFieldAsync in IDatabaseManager - Modifica GenerateSourceKey per richiedere sempre campo specifico - Implementa controllo IsTransferButtonEnabled per validazione form Breaking changes: - La generazione automatica delle chiavi sorgente è stata rimossa - Il campo chiave sorgente è ora obbligatorio quando si usa il sistema associazioni Fixes: Risolve problema di discovery schema vuoto con selezione database
This commit is contained in:
@@ -9,6 +9,7 @@ namespace CredentialManager.Data;
|
||||
public class CredentialDbContext : DbContext
|
||||
{
|
||||
public DbSet<CredentialEntity> Credentials { get; set; }
|
||||
public DbSet<RecordAssociation> RecordAssociations { get; set; }
|
||||
|
||||
public CredentialDbContext(DbContextOptions<CredentialDbContext> options) : base(options)
|
||||
{
|
||||
@@ -84,5 +85,55 @@ public class CredentialDbContext : DbContext
|
||||
|
||||
entity.HasIndex(e => e.IsActive);
|
||||
});
|
||||
|
||||
// Configurazione della tabella RecordAssociations
|
||||
modelBuilder.Entity<RecordAssociation>(entity =>
|
||||
{
|
||||
entity.ToTable("RecordAssociations");
|
||||
|
||||
entity.HasKey(e => e.Id);
|
||||
|
||||
entity.Property(e => e.SourceName)
|
||||
.IsRequired()
|
||||
.HasMaxLength(200);
|
||||
|
||||
entity.Property(e => e.SourceType)
|
||||
.IsRequired()
|
||||
.HasMaxLength(50);
|
||||
|
||||
entity.Property(e => e.SourceKey)
|
||||
.IsRequired()
|
||||
.HasMaxLength(500);
|
||||
|
||||
entity.Property(e => e.DestinationEntity)
|
||||
.IsRequired()
|
||||
.HasMaxLength(200);
|
||||
|
||||
entity.Property(e => e.DestinationId)
|
||||
.IsRequired()
|
||||
.HasMaxLength(200);
|
||||
|
||||
entity.Property(e => e.RestCredentialName)
|
||||
.IsRequired()
|
||||
.HasMaxLength(100);
|
||||
|
||||
entity.Property(e => e.AdditionalInfo)
|
||||
.HasMaxLength(2000);
|
||||
|
||||
// Valori di default
|
||||
entity.Property(e => e.IsActive)
|
||||
.HasDefaultValue(true);
|
||||
|
||||
// Indici
|
||||
entity.HasIndex(e => new { e.SourceName, e.SourceKey, e.DestinationEntity })
|
||||
.IsUnique()
|
||||
.HasDatabaseName("IX_RecordAssociations_Unique");
|
||||
|
||||
entity.HasIndex(e => e.SourceType);
|
||||
entity.HasIndex(e => e.DestinationEntity);
|
||||
entity.HasIndex(e => e.RestCredentialName);
|
||||
entity.HasIndex(e => e.IsActive);
|
||||
entity.HasIndex(e => e.CreatedAt);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace CredentialManager.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Aggiunge la tabella RecordAssociations per tracciare le associazioni tra record sorgente e destinazione
|
||||
/// </summary>
|
||||
public partial class AddRecordAssociations : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RecordAssociations",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||
.Annotation("Sqlite:Autoincrement", true),
|
||||
SourceName = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
|
||||
SourceType = table.Column<string>(type: "TEXT", maxLength: 50, nullable: false),
|
||||
SourceKey = table.Column<string>(type: "TEXT", maxLength: 500, nullable: false),
|
||||
DestinationEntity = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
|
||||
DestinationId = table.Column<string>(type: "TEXT", maxLength: 200, nullable: false),
|
||||
RestCredentialName = table.Column<string>(type: "TEXT", maxLength: 100, nullable: false),
|
||||
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "datetime('now')"),
|
||||
UpdatedAt = table.Column<DateTime>(type: "TEXT", nullable: true),
|
||||
IsActive = table.Column<bool>(type: "INTEGER", nullable: false, defaultValue: true),
|
||||
AdditionalInfo = table.Column<string>(type: "TEXT", maxLength: 2000, nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RecordAssociations", x => x.Id);
|
||||
});
|
||||
|
||||
// Indici per migliorare le performance
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RecordAssociations_Unique",
|
||||
table: "RecordAssociations",
|
||||
columns: new[] { "SourceName", "SourceKey", "DestinationEntity" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RecordAssociations_SourceType",
|
||||
table: "RecordAssociations",
|
||||
column: "SourceType");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RecordAssociations_DestinationEntity",
|
||||
table: "RecordAssociations",
|
||||
column: "DestinationEntity");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RecordAssociations_RestCredentialName",
|
||||
table: "RecordAssociations",
|
||||
column: "RestCredentialName");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RecordAssociations_IsActive",
|
||||
table: "RecordAssociations",
|
||||
column: "IsActive");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RecordAssociations_CreatedAt",
|
||||
table: "RecordAssociations",
|
||||
column: "CreatedAt");
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropTable(
|
||||
name: "RecordAssociations");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace CredentialManager.Models;
|
||||
|
||||
/// <summary>
|
||||
/// Entità per memorizzare le associazioni tra record sorgente e destinazione
|
||||
/// </summary>
|
||||
public class RecordAssociation
|
||||
{
|
||||
[Key]
|
||||
public int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Nome della sorgente dati (nome tabella/file/foglio)
|
||||
/// </summary>
|
||||
[Required]
|
||||
[MaxLength(200)]
|
||||
public string SourceName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Tipo di sorgente (database, file)
|
||||
/// </summary>
|
||||
[Required]
|
||||
[MaxLength(50)]
|
||||
public string SourceType { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Chiave del record sorgente (può essere un ID o una combinazione di campi)
|
||||
/// </summary>
|
||||
[Required]
|
||||
[MaxLength(500)]
|
||||
public string SourceKey { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Nome dell'entità di destinazione
|
||||
/// </summary>
|
||||
[Required]
|
||||
[MaxLength(200)]
|
||||
public string DestinationEntity { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// ID del record di destinazione
|
||||
/// </summary>
|
||||
[Required]
|
||||
[MaxLength(200)]
|
||||
public string DestinationId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Nome della credenziale REST utilizzata
|
||||
/// </summary>
|
||||
[Required]
|
||||
[MaxLength(100)]
|
||||
public string RestCredentialName { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// Data e ora della creazione dell'associazione
|
||||
/// </summary>
|
||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||
|
||||
/// <summary>
|
||||
/// Data e ora dell'ultimo aggiornamento
|
||||
/// </summary>
|
||||
public DateTime? UpdatedAt { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indica se l'associazione è ancora attiva
|
||||
/// </summary>
|
||||
public bool IsActive { get; set; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// Informazioni aggiuntive in formato JSON
|
||||
/// </summary>
|
||||
[MaxLength(2000)]
|
||||
public string? AdditionalInfo { get; set; }
|
||||
}
|
||||
@@ -64,15 +64,27 @@ public class DatabaseInitializer : IDatabaseInitializer
|
||||
{
|
||||
try
|
||||
{
|
||||
// Prova a fare una query semplice per verificare che la tabella esista
|
||||
// Verifica che la tabella principale Credentials esista
|
||||
await _context.Credentials.CountAsync();
|
||||
_logger.LogInformation("Verifica tabelle completata con successo");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning("Tabelle mancanti, ricreazione database...");
|
||||
_logger.LogInformation("Tabella Credentials verificata con successo");
|
||||
|
||||
// Se le tabelle non esistono, le ricreiamo
|
||||
// Verifica se la tabella RecordAssociations esiste, se non esiste la crea senza ricreare tutto il database
|
||||
try
|
||||
{
|
||||
await _context.RecordAssociations.CountAsync();
|
||||
_logger.LogInformation("Tabella RecordAssociations verificata con successo");
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.LogInformation("Tabella RecordAssociations non trovata, creazione tramite migrazione...");
|
||||
await CreateRecordAssociationsTableAsync();
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_logger.LogWarning("Tabella Credentials mancante, ricreazione database...");
|
||||
|
||||
// Solo se la tabella principale non esiste, ricreiamo tutto
|
||||
await _context.Database.EnsureDeletedAsync();
|
||||
await _context.Database.EnsureCreatedAsync();
|
||||
await SeedInitialDataAsync();
|
||||
@@ -142,7 +154,7 @@ public class DatabaseInitializer : IDatabaseInitializer
|
||||
{
|
||||
_logger.LogInformation("Verifica e applicazione migrazioni...");
|
||||
|
||||
// Verifica se la colonna RestServiceType esiste usando una query diretta
|
||||
// Migrazione 1: Verifica se la colonna RestServiceType esiste
|
||||
try
|
||||
{
|
||||
await _context.Database.ExecuteSqlRawAsync(
|
||||
@@ -157,6 +169,62 @@ public class DatabaseInitializer : IDatabaseInitializer
|
||||
"ALTER TABLE Credentials ADD COLUMN RestServiceType TEXT");
|
||||
_logger.LogInformation("Colonna RestServiceType aggiunta con successo");
|
||||
}
|
||||
|
||||
// Migrazione 2: Verifica se la tabella RecordAssociations esiste
|
||||
try
|
||||
{
|
||||
await _context.Database.ExecuteSqlRawAsync(
|
||||
"SELECT COUNT(*) FROM RecordAssociations LIMIT 1");
|
||||
_logger.LogInformation("Tabella RecordAssociations già presente");
|
||||
}
|
||||
catch (Microsoft.Data.Sqlite.SqliteException)
|
||||
{
|
||||
// La tabella non esiste, la creiamo
|
||||
_logger.LogInformation("Creazione tabella RecordAssociations...");
|
||||
|
||||
// Crea la tabella
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE TABLE RecordAssociations (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
SourceName TEXT NOT NULL,
|
||||
SourceType TEXT NOT NULL,
|
||||
SourceKey TEXT NOT NULL,
|
||||
DestinationEntity TEXT NOT NULL,
|
||||
DestinationId TEXT NOT NULL,
|
||||
RestCredentialName TEXT NOT NULL,
|
||||
CreatedAt TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UpdatedAt TEXT,
|
||||
IsActive INTEGER NOT NULL DEFAULT 1,
|
||||
AdditionalInfo TEXT
|
||||
)");
|
||||
|
||||
// Crea gli indici
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE UNIQUE INDEX IX_RecordAssociations_Unique
|
||||
ON RecordAssociations (SourceName, SourceKey, DestinationEntity)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_SourceType
|
||||
ON RecordAssociations (SourceType)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_DestinationEntity
|
||||
ON RecordAssociations (DestinationEntity)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_RestCredentialName
|
||||
ON RecordAssociations (RestCredentialName)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_IsActive
|
||||
ON RecordAssociations (IsActive)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_CreatedAt
|
||||
ON RecordAssociations (CreatedAt)");
|
||||
|
||||
_logger.LogInformation("Tabella RecordAssociations creata con successo");
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -164,4 +232,60 @@ public class DatabaseInitializer : IDatabaseInitializer
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task CreateRecordAssociationsTableAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
_logger.LogInformation("Creazione tabella RecordAssociations...");
|
||||
|
||||
// Crea la tabella
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE TABLE RecordAssociations (
|
||||
Id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
SourceName TEXT NOT NULL,
|
||||
SourceType TEXT NOT NULL,
|
||||
SourceKey TEXT NOT NULL,
|
||||
DestinationEntity TEXT NOT NULL,
|
||||
DestinationId TEXT NOT NULL,
|
||||
RestCredentialName TEXT NOT NULL,
|
||||
CreatedAt TEXT NOT NULL DEFAULT (datetime('now')),
|
||||
UpdatedAt TEXT,
|
||||
IsActive INTEGER NOT NULL DEFAULT 1,
|
||||
AdditionalInfo TEXT
|
||||
)");
|
||||
|
||||
// Crea gli indici
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE UNIQUE INDEX IX_RecordAssociations_Unique
|
||||
ON RecordAssociations (SourceName, SourceKey, DestinationEntity)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_SourceType
|
||||
ON RecordAssociations (SourceType)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_DestinationEntity
|
||||
ON RecordAssociations (DestinationEntity)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_RestCredentialName
|
||||
ON RecordAssociations (RestCredentialName)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_IsActive
|
||||
ON RecordAssociations (IsActive)");
|
||||
|
||||
await _context.Database.ExecuteSqlRawAsync(@"
|
||||
CREATE INDEX IX_RecordAssociations_CreatedAt
|
||||
ON RecordAssociations (CreatedAt)");
|
||||
|
||||
_logger.LogInformation("Tabella RecordAssociations creata con successo");
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella creazione della tabella RecordAssociations");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
using CredentialManager.Models;
|
||||
|
||||
namespace CredentialManager.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Interfaccia per il servizio di gestione delle associazioni record
|
||||
/// </summary>
|
||||
public interface IRecordAssociationService
|
||||
{
|
||||
/// <summary>
|
||||
/// Salva una nuova associazione tra record sorgente e destinazione
|
||||
/// </summary>
|
||||
Task<int> SaveAssociationAsync(RecordAssociation association);
|
||||
|
||||
/// <summary>
|
||||
/// Cerca un'associazione esistente tramite chiave sorgente
|
||||
/// </summary>
|
||||
Task<RecordAssociation?> FindAssociationAsync(string sourceName, string sourceKey, string destinationEntity);
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene tutte le associazioni per una sorgente specifica
|
||||
/// </summary>
|
||||
Task<List<RecordAssociation>> GetAssociationsBySourceAsync(string sourceName, string sourceType);
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene tutte le associazioni per un'entità di destinazione specifica
|
||||
/// </summary>
|
||||
Task<List<RecordAssociation>> GetAssociationsByDestinationAsync(string destinationEntity, string restCredentialName);
|
||||
|
||||
/// <summary>
|
||||
/// Ottiene tutte le associazioni attive
|
||||
/// </summary>
|
||||
Task<List<RecordAssociation>> GetAllActiveAssociationsAsync();
|
||||
|
||||
/// <summary>
|
||||
/// Aggiorna un'associazione esistente
|
||||
/// </summary>
|
||||
Task<bool> UpdateAssociationAsync(RecordAssociation association);
|
||||
|
||||
/// <summary>
|
||||
/// Disattiva un'associazione (soft delete)
|
||||
/// </summary>
|
||||
Task<bool> DeactivateAssociationAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Elimina definitivamente un'associazione
|
||||
/// </summary>
|
||||
Task<bool> DeleteAssociationAsync(int id);
|
||||
|
||||
/// <summary>
|
||||
/// Pulisce le associazioni obsolete (opzionale)
|
||||
/// </summary>
|
||||
Task<int> CleanupOldAssociationsAsync(TimeSpan olderThan);
|
||||
}
|
||||
@@ -0,0 +1,250 @@
|
||||
using CredentialManager.Data;
|
||||
using CredentialManager.Models;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace CredentialManager.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Servizio per la gestione delle associazioni tra record sorgente e destinazione
|
||||
/// </summary>
|
||||
public class RecordAssociationService : IRecordAssociationService
|
||||
{
|
||||
private readonly CredentialDbContext _context;
|
||||
private readonly ILogger<RecordAssociationService> _logger;
|
||||
|
||||
public RecordAssociationService(
|
||||
CredentialDbContext context,
|
||||
ILogger<RecordAssociationService> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<int> SaveAssociationAsync(RecordAssociation association)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Controlla se esiste già un'associazione per questa combinazione
|
||||
var existing = await _context.RecordAssociations
|
||||
.FirstOrDefaultAsync(ra =>
|
||||
ra.SourceName == association.SourceName &&
|
||||
ra.SourceKey == association.SourceKey &&
|
||||
ra.DestinationEntity == association.DestinationEntity &&
|
||||
ra.IsActive);
|
||||
|
||||
if (existing != null)
|
||||
{
|
||||
// Aggiorna l'associazione esistente
|
||||
existing.DestinationId = association.DestinationId;
|
||||
existing.RestCredentialName = association.RestCredentialName;
|
||||
existing.UpdatedAt = DateTime.UtcNow;
|
||||
existing.AdditionalInfo = association.AdditionalInfo;
|
||||
|
||||
_context.RecordAssociations.Update(existing);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Associazione aggiornata: {SourceName}/{SourceKey} -> {DestinationEntity}/{DestinationId}",
|
||||
association.SourceName, association.SourceKey, association.DestinationEntity, association.DestinationId);
|
||||
|
||||
return existing.Id;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Crea nuova associazione
|
||||
_context.RecordAssociations.Add(association);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Nuova associazione creata: {SourceName}/{SourceKey} -> {DestinationEntity}/{DestinationId}",
|
||||
association.SourceName, association.SourceKey, association.DestinationEntity, association.DestinationId);
|
||||
|
||||
return association.Id;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nel salvare l'associazione: {SourceName}/{SourceKey} -> {DestinationEntity}",
|
||||
association.SourceName, association.SourceKey, association.DestinationEntity);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<RecordAssociation?> FindAssociationAsync(string sourceName, string sourceKey, string destinationEntity)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _context.RecordAssociations
|
||||
.FirstOrDefaultAsync(ra =>
|
||||
ra.SourceName == sourceName &&
|
||||
ra.SourceKey == sourceKey &&
|
||||
ra.DestinationEntity == destinationEntity &&
|
||||
ra.IsActive);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella ricerca dell'associazione: {SourceName}/{SourceKey} -> {DestinationEntity}",
|
||||
sourceName, sourceKey, destinationEntity);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<RecordAssociation>> GetAssociationsBySourceAsync(string sourceName, string sourceType)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _context.RecordAssociations
|
||||
.Where(ra => ra.SourceName == sourceName && ra.SourceType == sourceType && ra.IsActive)
|
||||
.OrderByDescending(ra => ra.CreatedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nel recupero delle associazioni per sorgente: {SourceName} ({SourceType})",
|
||||
sourceName, sourceType);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<RecordAssociation>> GetAssociationsByDestinationAsync(string destinationEntity, string restCredentialName)
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _context.RecordAssociations
|
||||
.Where(ra => ra.DestinationEntity == destinationEntity &&
|
||||
ra.RestCredentialName == restCredentialName &&
|
||||
ra.IsActive)
|
||||
.OrderByDescending(ra => ra.CreatedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nel recupero delle associazioni per destinazione: {DestinationEntity} ({RestCredentialName})",
|
||||
destinationEntity, restCredentialName);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<RecordAssociation>> GetAllActiveAssociationsAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _context.RecordAssociations
|
||||
.Where(ra => ra.IsActive)
|
||||
.OrderByDescending(ra => ra.CreatedAt)
|
||||
.ToListAsync();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nel recupero di tutte le associazioni attive");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> UpdateAssociationAsync(RecordAssociation association)
|
||||
{
|
||||
try
|
||||
{
|
||||
var existing = await _context.RecordAssociations.FindAsync(association.Id);
|
||||
if (existing == null)
|
||||
{
|
||||
_logger.LogWarning("Associazione con ID {Id} non trovata per l'aggiornamento", association.Id);
|
||||
return false;
|
||||
}
|
||||
|
||||
existing.DestinationId = association.DestinationId;
|
||||
existing.RestCredentialName = association.RestCredentialName;
|
||||
existing.UpdatedAt = DateTime.UtcNow;
|
||||
existing.AdditionalInfo = association.AdditionalInfo;
|
||||
existing.IsActive = association.IsActive;
|
||||
|
||||
_context.RecordAssociations.Update(existing);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Associazione aggiornata: ID {Id}", association.Id);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nell'aggiornamento dell'associazione: ID {Id}", association.Id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeactivateAssociationAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var association = await _context.RecordAssociations.FindAsync(id);
|
||||
if (association == null)
|
||||
{
|
||||
_logger.LogWarning("Associazione con ID {Id} non trovata per la disattivazione", id);
|
||||
return false;
|
||||
}
|
||||
|
||||
association.IsActive = false;
|
||||
association.UpdatedAt = DateTime.UtcNow;
|
||||
|
||||
_context.RecordAssociations.Update(association);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Associazione disattivata: ID {Id}", id);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella disattivazione dell'associazione: ID {Id}", id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<bool> DeleteAssociationAsync(int id)
|
||||
{
|
||||
try
|
||||
{
|
||||
var association = await _context.RecordAssociations.FindAsync(id);
|
||||
if (association == null)
|
||||
{
|
||||
_logger.LogWarning("Associazione con ID {Id} non trovata per l'eliminazione", id);
|
||||
return false;
|
||||
}
|
||||
|
||||
_context.RecordAssociations.Remove(association);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Associazione eliminata: ID {Id}", id);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nell'eliminazione dell'associazione: ID {Id}", id);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<int> CleanupOldAssociationsAsync(TimeSpan olderThan)
|
||||
{
|
||||
try
|
||||
{
|
||||
var cutoffDate = DateTime.UtcNow - olderThan;
|
||||
var oldAssociations = await _context.RecordAssociations
|
||||
.Where(ra => ra.CreatedAt < cutoffDate && !ra.IsActive)
|
||||
.ToListAsync();
|
||||
|
||||
if (oldAssociations.Any())
|
||||
{
|
||||
_context.RecordAssociations.RemoveRange(oldAssociations);
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Pulite {Count} associazioni obsolete più vecchie di {Cutoff}",
|
||||
oldAssociations.Count, cutoffDate);
|
||||
}
|
||||
|
||||
return oldAssociations.Count;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Errore nella pulizia delle associazioni obsolete");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user