b9670ae426
- Creato modello FieldMappingEntry per gestione unificata di field mapping e default values - Aggiunta colonna DefaultValuesJson alla tabella DataCouplerProfile (max 4000 caratteri) - Implementata UI con toggle per selezionare modalità Mapping o Default - Supporto per 9 tipi di dati: string, int, long, decimal, double, float, boolean, datetime, datetimeoffset - Aggiornata logica TransformRecordToRestEntity per applicare valori default dopo field mapping - Implementata serializzazione/deserializzazione DefaultValues in DataCouplerProfileService - Sistema completo di salvataggio/caricamento valori default nei profili - Migrazione database AddDefaultValuesJsonToProfile creata e applicata
362 lines
12 KiB
C#
362 lines
12 KiB
C#
using Microsoft.EntityFrameworkCore;
|
|
using CredentialManager.Data;
|
|
using CredentialManager.Models;
|
|
using System.Text.Json;
|
|
|
|
namespace CredentialManager.Services;
|
|
|
|
/// <summary>
|
|
/// Implementazione del servizio per la gestione dei profili Data Coupler
|
|
/// </summary>
|
|
public class DataCouplerProfileService : IDataCouplerProfileService
|
|
{
|
|
private readonly CredentialDbContext _context;
|
|
|
|
public DataCouplerProfileService(CredentialDbContext context)
|
|
{
|
|
_context = context;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ottiene tutti i profili attivi
|
|
/// </summary>
|
|
public async Task<IEnumerable<DataCouplerProfile>> GetAllProfilesAsync()
|
|
{
|
|
return await _context.DataCouplerProfiles
|
|
.Include(p => p.SourceCredential)
|
|
.Include(p => p.DestinationCredential)
|
|
.Where(p => p.IsActive)
|
|
.OrderByDescending(p => p.LastUsedAt)
|
|
.ThenByDescending(p => p.CreatedAt)
|
|
.ToListAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ottiene tutti i profili per nome (inclusi quelli inattivi)
|
|
/// </summary>
|
|
public async Task<DataCouplerProfile?> GetProfileByNameIncludingInactiveAsync(string name)
|
|
{
|
|
return await _context.DataCouplerProfiles
|
|
.Include(p => p.SourceCredential)
|
|
.Include(p => p.DestinationCredential)
|
|
.FirstOrDefaultAsync(p => p.Name.ToLower() == name.ToLower());
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ottiene un profilo per ID
|
|
/// </summary>
|
|
public async Task<DataCouplerProfile?> GetProfileByIdAsync(int id)
|
|
{
|
|
return await _context.DataCouplerProfiles
|
|
.Include(p => p.SourceCredential)
|
|
.Include(p => p.DestinationCredential)
|
|
.FirstOrDefaultAsync(p => p.Id == id && p.IsActive);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Ottiene un profilo per nome
|
|
/// </summary>
|
|
public async Task<DataCouplerProfile?> GetProfileByNameAsync(string name)
|
|
{
|
|
return await _context.DataCouplerProfiles
|
|
.Include(p => p.SourceCredential)
|
|
.Include(p => p.DestinationCredential)
|
|
.FirstOrDefaultAsync(p => p.Name == name && p.IsActive);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Salva un nuovo profilo
|
|
/// </summary>
|
|
public async Task<DataCouplerProfile> SaveProfileAsync(DataCouplerProfile profile)
|
|
{
|
|
profile.CreatedAt = DateTime.UtcNow;
|
|
profile.IsActive = true;
|
|
|
|
_context.DataCouplerProfiles.Add(profile);
|
|
await _context.SaveChangesAsync();
|
|
|
|
return profile;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Aggiorna un profilo esistente
|
|
/// </summary>
|
|
public async Task<DataCouplerProfile> UpdateProfileAsync(DataCouplerProfile profile)
|
|
{
|
|
var existingProfile = await _context.DataCouplerProfiles
|
|
.FirstOrDefaultAsync(p => p.Id == profile.Id);
|
|
|
|
if (existingProfile == null)
|
|
{
|
|
throw new InvalidOperationException($"Profilo con ID {profile.Id} non trovato");
|
|
}
|
|
|
|
// Aggiorna le proprietà (evita di aggiornare il nome se è uguale per evitare unique constraint)
|
|
if (!string.Equals(existingProfile.Name, profile.Name, StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
existingProfile.Name = profile.Name;
|
|
}
|
|
|
|
existingProfile.Description = profile.Description;
|
|
existingProfile.SourceType = profile.SourceType;
|
|
existingProfile.SourceCredentialId = profile.SourceCredentialId;
|
|
existingProfile.SourceSchema = profile.SourceSchema;
|
|
existingProfile.SourceTable = profile.SourceTable;
|
|
existingProfile.SourceFilePath = profile.SourceFilePath;
|
|
existingProfile.DestinationType = profile.DestinationType;
|
|
existingProfile.DestinationCredentialId = profile.DestinationCredentialId;
|
|
existingProfile.DestinationSchema = profile.DestinationSchema;
|
|
existingProfile.DestinationTable = profile.DestinationTable;
|
|
existingProfile.DestinationEndpoint = profile.DestinationEndpoint;
|
|
existingProfile.FieldMappingJson = profile.FieldMappingJson;
|
|
existingProfile.ExternalIdRelationshipsJson = profile.ExternalIdRelationshipsJson;
|
|
existingProfile.SourceKeyField = profile.SourceKeyField;
|
|
existingProfile.UseRecordAssociations = profile.UseRecordAssociations;
|
|
existingProfile.IsActive = profile.IsActive;
|
|
|
|
await _context.SaveChangesAsync();
|
|
return existingProfile;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Elimina un profilo (soft delete)
|
|
/// </summary>
|
|
public async Task<bool> DeleteProfileAsync(int id)
|
|
{
|
|
var profile = await _context.DataCouplerProfiles
|
|
.FirstOrDefaultAsync(p => p.Id == id);
|
|
|
|
if (profile == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
profile.IsActive = false;
|
|
await _context.SaveChangesAsync();
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Aggiorna la data di ultimo utilizzo di un profilo
|
|
/// </summary>
|
|
public async Task UpdateLastUsedAsync(int id)
|
|
{
|
|
var profile = await _context.DataCouplerProfiles
|
|
.FirstOrDefaultAsync(p => p.Id == id);
|
|
|
|
if (profile != null)
|
|
{
|
|
profile.LastUsedAt = DateTime.UtcNow;
|
|
await _context.SaveChangesAsync();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Verifica se esiste un profilo con il nome specificato
|
|
/// </summary>
|
|
public async Task<bool> ProfileExistsAsync(string name, int? excludeId = null)
|
|
{
|
|
var query = _context.DataCouplerProfiles
|
|
.Where(p => p.Name == name && p.IsActive);
|
|
|
|
if (excludeId.HasValue)
|
|
{
|
|
query = query.Where(p => p.Id != excludeId.Value);
|
|
}
|
|
|
|
return await query.AnyAsync();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializza la lista di mapping dei campi in JSON
|
|
/// </summary>
|
|
public string SerializeFieldMappings(List<FieldMappingDto>? mappings)
|
|
{
|
|
if (mappings == null || !mappings.Any())
|
|
return string.Empty;
|
|
|
|
return JsonSerializer.Serialize(mappings, new JsonSerializerOptions
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deserializza il JSON dei mapping dei campi
|
|
/// </summary>
|
|
public List<FieldMappingDto> DeserializeFieldMappings(string? json)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(json))
|
|
return new List<FieldMappingDto>();
|
|
|
|
try
|
|
{
|
|
return JsonSerializer.Deserialize<List<FieldMappingDto>>(json, new JsonSerializerOptions
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
|
}) ?? new List<FieldMappingDto>();
|
|
}
|
|
catch
|
|
{
|
|
return new List<FieldMappingDto>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializza la lista di External ID Relationships in JSON
|
|
/// </summary>
|
|
public string SerializeExternalIdRelationships(List<ExternalIdRelationshipDto>? relationships)
|
|
{
|
|
if (relationships == null || !relationships.Any())
|
|
return string.Empty;
|
|
|
|
return JsonSerializer.Serialize(relationships, new JsonSerializerOptions
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// <summary>
|
|
/// Deserializza il JSON delle External ID Relationships
|
|
/// </summary>
|
|
public List<ExternalIdRelationshipDto> DeserializeExternalIdRelationships(string? json)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(json))
|
|
return new List<ExternalIdRelationshipDto>();
|
|
|
|
try
|
|
{
|
|
return JsonSerializer.Deserialize<List<ExternalIdRelationshipDto>>(json, new JsonSerializerOptions
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
|
}) ?? new List<ExternalIdRelationshipDto>();
|
|
}
|
|
catch
|
|
{
|
|
return new List<ExternalIdRelationshipDto>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Serializza i default values in JSON
|
|
/// </summary>
|
|
public string SerializeDefaultValues(Dictionary<string, (object? Value, string? Type)>? defaultValues)
|
|
{
|
|
if (defaultValues == null || !defaultValues.Any())
|
|
return string.Empty;
|
|
|
|
// Converti in un formato serializzabile (Dictionary<string, DefaultValueDto>)
|
|
var serializable = new Dictionary<string, DefaultValueDto>();
|
|
foreach (var entry in defaultValues)
|
|
{
|
|
serializable[entry.Key] = new DefaultValueDto
|
|
{
|
|
Value = entry.Value.Value,
|
|
Type = entry.Value.Type
|
|
};
|
|
}
|
|
|
|
return JsonSerializer.Serialize(serializable, new JsonSerializerOptions
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
|
});
|
|
}
|
|
|
|
/// <summary>
|
|
/// Deserializza il JSON dei default values
|
|
/// </summary>
|
|
public Dictionary<string, (object? Value, string? Type)> DeserializeDefaultValues(string? json)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(json))
|
|
return new Dictionary<string, (object?, string?)>();
|
|
|
|
try
|
|
{
|
|
var deserialized = JsonSerializer.Deserialize<Dictionary<string, DefaultValueDto>>(json, new JsonSerializerOptions
|
|
{
|
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
|
});
|
|
|
|
if (deserialized == null)
|
|
return new Dictionary<string, (object?, string?)>();
|
|
|
|
// Converti nel formato tuple
|
|
var result = new Dictionary<string, (object?, string?)>();
|
|
foreach (var entry in deserialized)
|
|
{
|
|
result[entry.Key] = (entry.Value.Value, entry.Value.Type);
|
|
}
|
|
|
|
return result;
|
|
}
|
|
catch
|
|
{
|
|
return new Dictionary<string, (object?, string?)>();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converte un DataCouplerProfile in DTO
|
|
/// </summary>
|
|
public DataCouplerProfileDto ToDto(DataCouplerProfile profile)
|
|
{
|
|
return new DataCouplerProfileDto
|
|
{
|
|
Id = profile.Id,
|
|
Name = profile.Name,
|
|
Description = profile.Description,
|
|
SourceType = profile.SourceType,
|
|
SourceCredentialId = profile.SourceCredentialId,
|
|
SourceCredentialName = profile.SourceCredential?.Name,
|
|
SourceDatabaseName = profile.SourceDatabaseName,
|
|
SourceSchema = profile.SourceSchema,
|
|
SourceTable = profile.SourceTable,
|
|
SourceCustomQuery = profile.SourceCustomQuery,
|
|
SourceFilePath = profile.SourceFilePath,
|
|
DestinationType = profile.DestinationType,
|
|
DestinationCredentialId = profile.DestinationCredentialId,
|
|
DestinationCredentialName = profile.DestinationCredential?.Name,
|
|
DestinationSchema = profile.DestinationSchema,
|
|
DestinationTable = profile.DestinationTable,
|
|
DestinationEndpoint = profile.DestinationEndpoint,
|
|
FieldMappings = DeserializeFieldMappings(profile.FieldMappingJson),
|
|
DefaultValues = DeserializeDefaultValues(profile.DefaultValuesJson),
|
|
ExternalIdRelationships = DeserializeExternalIdRelationships(profile.ExternalIdRelationshipsJson),
|
|
SourceKeyField = profile.SourceKeyField,
|
|
UseRecordAssociations = profile.UseRecordAssociations
|
|
};
|
|
}
|
|
|
|
/// <summary>
|
|
/// Converte un DTO in DataCouplerProfile
|
|
/// </summary>
|
|
public DataCouplerProfile FromDto(DataCouplerProfileDto dto, string? createdBy = null)
|
|
{
|
|
return new DataCouplerProfile
|
|
{
|
|
Id = dto.Id ?? 0,
|
|
Name = dto.Name,
|
|
Description = dto.Description,
|
|
SourceType = dto.SourceType,
|
|
SourceCredentialId = dto.SourceCredentialId,
|
|
SourceDatabaseName = dto.SourceDatabaseName,
|
|
SourceSchema = dto.SourceSchema,
|
|
SourceTable = dto.SourceTable,
|
|
SourceCustomQuery = dto.SourceCustomQuery,
|
|
SourceFilePath = dto.SourceFilePath,
|
|
DestinationType = dto.DestinationType,
|
|
DestinationCredentialId = dto.DestinationCredentialId,
|
|
DestinationSchema = dto.DestinationSchema,
|
|
DestinationTable = dto.DestinationTable,
|
|
DestinationEndpoint = dto.DestinationEndpoint,
|
|
FieldMappingJson = SerializeFieldMappings(dto.FieldMappings),
|
|
DefaultValuesJson = SerializeDefaultValues(dto.DefaultValues),
|
|
ExternalIdRelationshipsJson = SerializeExternalIdRelationships(dto.ExternalIdRelationships),
|
|
SourceKeyField = dto.SourceKeyField,
|
|
UseRecordAssociations = dto.UseRecordAssociations,
|
|
CreatedBy = createdBy
|
|
};
|
|
}
|
|
}
|