feat: Implement ExistingDatabaseContext for managing existing databases with customizable naming strategies and auto-discovery of entities
feat: Add SqlServerSchemaProvider for extracting database schema information from SQL Server feat: Introduce DatabaseType and NamingStrategy enums for better database management and naming conventions feat: Create IDatabaseDiscovery and IDatabaseManager interfaces for database operations and metadata retrieval feat: Develop REST service client architecture with BaseRestServiceClient and SAP Business One specific implementation feat: Implement REST service discovery page with UI for connecting to SAP Business One Service Layer and displaying discovered entities
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Data.SqlClient;
|
||||
using System.Threading.Tasks;
|
||||
using DataConnection.Interfaces;
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace DataConnection.EF.DatabaseDiscovery;
|
||||
|
||||
/// <summary>
|
||||
/// Implementazione di IDatabaseDiscovery per SQL Server
|
||||
/// </summary>
|
||||
public class SqlServerDatabaseDiscovery : IDatabaseDiscovery
|
||||
{
|
||||
private static readonly List<string> _systemDatabases = new List<string>
|
||||
{
|
||||
"master", "tempdb", "model", "msdb", "distribution"
|
||||
};
|
||||
|
||||
public async Task<List<string>> GetAvailableDatabasesAsync(string serverConnectionString, bool excludeSystemDatabases = true)
|
||||
{
|
||||
List<string> databases = new List<string>();
|
||||
|
||||
using (SqlConnection connection = new SqlConnection(serverConnectionString))
|
||||
{
|
||||
await connection.OpenAsync();
|
||||
|
||||
// Query per ottenere tutti i database sul server
|
||||
string query = @"
|
||||
SELECT name
|
||||
FROM sys.databases
|
||||
WHERE state_desc = 'ONLINE'";
|
||||
|
||||
if (excludeSystemDatabases)
|
||||
{
|
||||
query += " AND name NOT IN ('master', 'tempdb', 'model', 'msdb', 'distribution')";
|
||||
}
|
||||
|
||||
query += " ORDER BY name";
|
||||
|
||||
using (SqlCommand command = new SqlCommand(query, connection))
|
||||
{
|
||||
using (SqlDataReader reader = await command.ExecuteReaderAsync())
|
||||
{
|
||||
while (await reader.ReadAsync())
|
||||
{
|
||||
databases.Add(reader.GetString(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return databases;
|
||||
}
|
||||
|
||||
public async Task<Dictionary<string, DatabaseInfo>> GetDatabasesInfoAsync(string serverConnectionString, bool excludeSystemDatabases = true)
|
||||
{
|
||||
Dictionary<string, DatabaseInfo> databasesInfo = new Dictionary<string, DatabaseInfo>();
|
||||
|
||||
using (SqlConnection connection = new SqlConnection(serverConnectionString))
|
||||
{
|
||||
await connection.OpenAsync();
|
||||
|
||||
// Query per ottenere informazioni dettagliate sui database
|
||||
string query = @"
|
||||
SELECT
|
||||
d.name,
|
||||
CAST((SELECT SUM(CAST(size AS BIGINT)) * 8.0 / 1024 FROM sys.master_files WHERE database_id = d.database_id) AS DECIMAL(18,2)) AS size_mb,
|
||||
d.create_date,
|
||||
d.state_desc,
|
||||
SUSER_SNAME(d.owner_sid) AS owner,
|
||||
CASE WHEN d.name IN ('master', 'tempdb', 'model', 'msdb', 'distribution') THEN 1 ELSE 0 END AS is_system_db
|
||||
FROM sys.databases d
|
||||
WHERE state_desc = 'ONLINE'";
|
||||
|
||||
if (excludeSystemDatabases)
|
||||
{
|
||||
query += " AND d.name NOT IN ('master', 'tempdb', 'model', 'msdb', 'distribution')";
|
||||
}
|
||||
|
||||
query += " ORDER BY d.name";
|
||||
|
||||
using (SqlCommand command = new SqlCommand(query, connection))
|
||||
{
|
||||
using (SqlDataReader reader = await command.ExecuteReaderAsync())
|
||||
{
|
||||
while (await reader.ReadAsync())
|
||||
{
|
||||
string dbName = reader.GetString(0);
|
||||
|
||||
databasesInfo[dbName] = new DatabaseInfo
|
||||
{
|
||||
Name = dbName,
|
||||
SizeMB = reader.IsDBNull(1) ? 0 : Convert.ToDouble(reader.GetDecimal(1)),
|
||||
CreationDate = reader.GetDateTime(2),
|
||||
Status = reader.GetString(3),
|
||||
Owner = reader.IsDBNull(4) ? null : reader.GetString(4),
|
||||
IsSystemDatabase = reader.GetInt32(5) == 1
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return databasesInfo;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using DataConnection.EF.SchemaProviders;
|
||||
using DataConnection.Enums;
|
||||
using DataConnection.Interfaces;
|
||||
|
||||
namespace DataConnection.EF;
|
||||
|
||||
/// <summary>
|
||||
/// Factory per la creazione di provider di schema del database
|
||||
/// </summary>
|
||||
public class DatabaseSchemaProviderFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Crea un provider di schema in base al tipo di database
|
||||
/// </summary>
|
||||
/// <param name="databaseType">Tipo di database</param>
|
||||
/// <returns>Provider di schema appropriato</returns>
|
||||
public static IDatabaseSchemaProvider CreateProvider(DatabaseType databaseType)
|
||||
{
|
||||
return databaseType switch
|
||||
{
|
||||
DatabaseType.SqlServer => new SqlServerSchemaProvider(),
|
||||
// Aggiungere qui altri provider quando implementati
|
||||
// DatabaseType.MySql => new MySqlSchemaProvider(),
|
||||
// DatabaseType.PostgreSql => new PostgreSqlSchemaProvider(),
|
||||
// DatabaseType.Oracle => new OracleSchemaProvider(),
|
||||
// DatabaseType.Sqlite => new SqliteSchemaProvider(),
|
||||
// DatabaseType.DB2 => new DB2SchemaProvider(),
|
||||
// DatabaseType.SapHana => new SapHanaSchemaProvider(),
|
||||
_ => throw new NotSupportedException($"Tipo di database non supportato per l'estrazione dello schema: {databaseType}")
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using DataConnection.Interfaces;
|
||||
using DataConnection.EF.DatabaseDiscovery;
|
||||
using DataConnection.Enums;
|
||||
|
||||
namespace DataConnection.EF;
|
||||
|
||||
/// <summary>
|
||||
/// Opzioni per la configurazione del manager di database esistenti
|
||||
/// </summary>
|
||||
public class DbManagerOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuratore del DbContext
|
||||
/// </summary>
|
||||
public Action<DbContextOptionsBuilder> DbContextConfigurator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Configuratore del modello del database
|
||||
/// </summary>
|
||||
public Action<ModelBuilder> ModelConfigurator { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Flag che indica se la scoperta automatica delle entità è abilitata
|
||||
/// </summary>
|
||||
public bool EnableAutoDiscovery { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Assembly da cui caricare automaticamente le entità (se EnableAutoDiscovery = true)
|
||||
/// </summary>
|
||||
public System.Reflection.Assembly EntityAssembly { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Namespace in cui cercare le entità (se EnableAutoDiscovery = true)
|
||||
/// </summary>
|
||||
public string EntityNamespace { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Timeout per le operazioni del database (in secondi)
|
||||
/// </summary>
|
||||
public int CommandTimeout { get; set; } = 30;
|
||||
|
||||
/// <summary>
|
||||
/// Strategia di mappatura dei nomi (CamelCase, PascalCase, SnakeCase)
|
||||
/// </summary>
|
||||
public NamingStrategy NamingStrategy { get; set; } = NamingStrategy.Default;
|
||||
|
||||
/// <summary>
|
||||
/// Servizio per la scoperta dei database disponibili sul server
|
||||
/// </summary>
|
||||
public IDatabaseDiscovery DatabaseDiscoveryService { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Stringa di connessione a livello di server (senza specificare il database)
|
||||
/// </summary>
|
||||
public string ServerConnectionString { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Nome del database a cui connettersi
|
||||
/// </summary>
|
||||
public string DatabaseName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Tipo di database (SqlServer, MySql, ecc.)
|
||||
/// </summary>
|
||||
public DatabaseType DatabaseType { get; set; } = DatabaseType.SqlServer;
|
||||
|
||||
/// <summary>
|
||||
/// Configura automaticamente il servizio di scoperta database in base al tipo di database
|
||||
/// </summary>
|
||||
/// <param name="databaseType">Tipo di database</param>
|
||||
public void ConfigureDatabaseDiscovery(DatabaseType databaseType)
|
||||
{
|
||||
DatabaseType = databaseType;
|
||||
|
||||
switch (databaseType)
|
||||
{
|
||||
case DatabaseType.SqlServer:
|
||||
DatabaseDiscoveryService = new SqlServerDatabaseDiscovery();
|
||||
break;
|
||||
// case DatabaseType.MySql:
|
||||
// DatabaseDiscoveryService = new MySqlDatabaseDiscovery();
|
||||
// break;
|
||||
// Altri tipi di database possono essere aggiunti qui
|
||||
default:
|
||||
throw new NotSupportedException($"Tipo di database non supportato: {databaseType}");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Costruisce una stringa di connessione completa includendo il database selezionato
|
||||
/// </summary>
|
||||
public string BuildFullConnectionString()
|
||||
{
|
||||
if (string.IsNullOrEmpty(ServerConnectionString))
|
||||
{
|
||||
throw new InvalidOperationException("La stringa di connessione al server non è stata specificata");
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(DatabaseName))
|
||||
{
|
||||
return ServerConnectionString;
|
||||
}
|
||||
|
||||
// Per SQL Server
|
||||
if (ServerConnectionString.Contains("Initial Catalog=") || ServerConnectionString.Contains("Database="))
|
||||
{
|
||||
// Sostituisci il database esistente
|
||||
var modifiedString = System.Text.RegularExpressions.Regex.Replace(
|
||||
ServerConnectionString,
|
||||
@"(Initial Catalog|Database)=([^;]*)",
|
||||
$"$1={DatabaseName}");
|
||||
|
||||
return modifiedString;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Aggiungi il database
|
||||
return ServerConnectionString + $";Database={DatabaseName}";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading.Tasks;
|
||||
using DataConnection.Interfaces;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
|
||||
namespace DataConnection.EF;
|
||||
|
||||
/// <summary>
|
||||
/// Implementazione di IExistingDatabaseManager basata su Entity Framework Core
|
||||
/// </summary>
|
||||
public class EFCoreDatabaseManager : IDatabaseManager
|
||||
{
|
||||
private readonly DbManagerOptions _options;
|
||||
private ExistingDatabaseContext _context;
|
||||
|
||||
public EFCoreDatabaseManager(DbManagerOptions options)
|
||||
{
|
||||
_options = options ?? throw new ArgumentNullException(nameof(options));
|
||||
InitializeContext();
|
||||
}
|
||||
|
||||
private void InitializeContext()
|
||||
{
|
||||
var optionsBuilder = new DbContextOptionsBuilder<ExistingDatabaseContext>();
|
||||
_options.DbContextConfigurator(optionsBuilder);
|
||||
|
||||
_context = new ExistingDatabaseContext(
|
||||
optionsBuilder.Options,
|
||||
_options.ModelConfigurator,
|
||||
_options.EnableAutoDiscovery,
|
||||
_options.EntityAssembly,
|
||||
_options.EntityNamespace,
|
||||
_options.NamingStrategy);
|
||||
}
|
||||
|
||||
public async Task<bool> TestConnectionAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
return await _context.Database.CanConnectAsync();
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<T>> GetAsync<T>(
|
||||
Expression<Func<T, bool>> filter = null,
|
||||
Func<IQueryable<T>, IOrderedQueryable<T>> orderBy = null,
|
||||
string includeProperties = "",
|
||||
int? skip = null,
|
||||
int? take = null) where T : class
|
||||
{
|
||||
IQueryable<T> query = _context.Set<T>();
|
||||
|
||||
if (filter != null)
|
||||
{
|
||||
query = query.Where(filter);
|
||||
}
|
||||
|
||||
foreach (var includeProperty in includeProperties.Split
|
||||
(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries))
|
||||
{
|
||||
query = query.Include(includeProperty);
|
||||
}
|
||||
|
||||
if (orderBy != null)
|
||||
{
|
||||
query = orderBy(query);
|
||||
}
|
||||
|
||||
if (skip.HasValue)
|
||||
{
|
||||
query = query.Skip(skip.Value);
|
||||
}
|
||||
|
||||
if (take.HasValue)
|
||||
{
|
||||
query = query.Take(take.Value);
|
||||
}
|
||||
|
||||
return await query.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<T> GetByIdAsync<T>(object id) where T : class
|
||||
{
|
||||
return await _context.Set<T>().FindAsync(id);
|
||||
}
|
||||
|
||||
public async Task<IEnumerable<T>> ExecuteQueryAsync<T>(string sql, params object[] parameters) where T : class
|
||||
{
|
||||
return await _context.Set<T>().FromSqlRaw(sql, parameters).ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<int> ExecuteCommandAsync(string sql, params object[] parameters)
|
||||
{
|
||||
return await _context.Database.ExecuteSqlRawAsync(sql, parameters);
|
||||
}
|
||||
|
||||
public async Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync()
|
||||
{
|
||||
try
|
||||
{
|
||||
// Assicurarsi che il contesto sia connesso
|
||||
await _context.Database.OpenConnectionAsync();
|
||||
|
||||
// Usa la factory per ottenere il provider appropriato in base al tipo di database
|
||||
var schemaProvider = DatabaseSchemaProviderFactory.CreateProvider(_options.DatabaseType);
|
||||
|
||||
// Usa il provider per ottenere lo schema
|
||||
return await schemaProvider.GetDatabaseSchemaAsync(_context.Database.GetConnectionString());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Errore nel recupero dello schema del database: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_context?.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using DataConnection.Enums;
|
||||
|
||||
namespace DataConnection.EF;
|
||||
|
||||
/// <summary>
|
||||
/// DbContext per gestione database esistenti
|
||||
/// </summary>
|
||||
public class ExistingDatabaseContext : DbContext
|
||||
{
|
||||
private readonly Action<ModelBuilder> _modelConfigurator;
|
||||
private readonly bool _enableAutoDiscovery;
|
||||
private readonly Assembly _entityAssembly;
|
||||
private readonly string _entityNamespace;
|
||||
private readonly NamingStrategy _namingStrategy;
|
||||
|
||||
public ExistingDatabaseContext(
|
||||
DbContextOptions options,
|
||||
Action<ModelBuilder> modelConfigurator = null,
|
||||
bool enableAutoDiscovery = false,
|
||||
Assembly entityAssembly = null,
|
||||
string entityNamespace = null,
|
||||
NamingStrategy namingStrategy = NamingStrategy.Default)
|
||||
: base(options)
|
||||
{
|
||||
_modelConfigurator = modelConfigurator;
|
||||
_enableAutoDiscovery = enableAutoDiscovery;
|
||||
_entityAssembly = entityAssembly;
|
||||
_entityNamespace = entityNamespace;
|
||||
_namingStrategy = namingStrategy;
|
||||
}
|
||||
|
||||
protected override void OnModelCreating(ModelBuilder modelBuilder)
|
||||
{
|
||||
base.OnModelCreating(modelBuilder);
|
||||
|
||||
// Applica la strategia di mappatura dei nomi
|
||||
ApplyNamingStrategy(modelBuilder);
|
||||
|
||||
// Applica la configurazione personalizzata se fornita
|
||||
_modelConfigurator?.Invoke(modelBuilder);
|
||||
|
||||
// Scoperta automatica delle entità
|
||||
if (_enableAutoDiscovery && _entityAssembly != null)
|
||||
{
|
||||
DiscoverEntities(modelBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
private void ApplyNamingStrategy(ModelBuilder modelBuilder)
|
||||
{
|
||||
switch (_namingStrategy)
|
||||
{
|
||||
case NamingStrategy.CamelCase:
|
||||
foreach (var entity in modelBuilder.Model.GetEntityTypes())
|
||||
{
|
||||
// Converti il nome della tabella in camelCase
|
||||
string tableName = entity.GetTableName();
|
||||
if (!string.IsNullOrEmpty(tableName) && char.IsUpper(tableName[0]))
|
||||
entity.SetTableName(char.ToLower(tableName[0]) + tableName.Substring(1));
|
||||
|
||||
// Converti i nomi delle proprietà in camelCase
|
||||
foreach (var property in entity.GetProperties())
|
||||
{
|
||||
string propertyName = property.GetColumnName();
|
||||
if (!string.IsNullOrEmpty(propertyName) && char.IsUpper(propertyName[0]))
|
||||
property.SetColumnName(char.ToLower(propertyName[0]) + propertyName.Substring(1));
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case NamingStrategy.SnakeCase:
|
||||
foreach (var entity in modelBuilder.Model.GetEntityTypes())
|
||||
{
|
||||
// Converti il nome della tabella in snake_case
|
||||
string tableName = entity.GetTableName();
|
||||
if (!string.IsNullOrEmpty(tableName))
|
||||
entity.SetTableName(ConvertToSnakeCase(tableName));
|
||||
|
||||
// Converti i nomi delle proprietà in snake_case
|
||||
foreach (var property in entity.GetProperties())
|
||||
{
|
||||
string propertyName = property.GetColumnName();
|
||||
if (!string.IsNullOrEmpty(propertyName))
|
||||
property.SetColumnName(ConvertToSnakeCase(propertyName));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private string ConvertToSnakeCase(string input)
|
||||
{
|
||||
if (string.IsNullOrEmpty(input))
|
||||
return input;
|
||||
|
||||
return string.Concat(input.Select((x, i) => i > 0 && char.IsUpper(x) ? "_" + x.ToString() : x.ToString())).ToLower();
|
||||
}
|
||||
|
||||
private void DiscoverEntities(ModelBuilder modelBuilder)
|
||||
{
|
||||
// Trova tutte le classi nel namespace specificato che potrebbero essere entità
|
||||
var entityTypes = _entityAssembly.GetTypes()
|
||||
.Where(t => !string.IsNullOrEmpty(_entityNamespace)
|
||||
? t.Namespace == _entityNamespace
|
||||
: true)
|
||||
.Where(t => t.IsClass && !t.IsAbstract && t.GetConstructor(Type.EmptyTypes) != null)
|
||||
.ToList();
|
||||
|
||||
// Metodo generico per aggiungere entità al modello
|
||||
var entityMethod = typeof(ModelBuilder).GetMethod("Entity", new Type[0]);
|
||||
|
||||
foreach (var entityType in entityTypes)
|
||||
{
|
||||
// Usa reflection per chiamare il metodo generico Entity<T>()
|
||||
var genericMethod = entityMethod.MakeGenericMethod(entityType);
|
||||
genericMethod.Invoke(modelBuilder, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data;
|
||||
using System.Threading.Tasks;
|
||||
using DataConnection.Interfaces;
|
||||
using Microsoft.Data.SqlClient;
|
||||
|
||||
namespace DataConnection.EF.SchemaProviders;
|
||||
|
||||
/// <summary>
|
||||
/// Provider di schema per database SQL Server
|
||||
/// </summary>
|
||||
public class SqlServerSchemaProvider : IDatabaseSchemaProvider
|
||||
{
|
||||
public async Task<IDictionary<string, IEnumerable<DbColumnInfo>>> GetDatabaseSchemaAsync(string connectionString)
|
||||
{
|
||||
var result = new Dictionary<string, IEnumerable<DbColumnInfo>>();
|
||||
|
||||
try
|
||||
{
|
||||
using (var connection = new SqlConnection(connectionString))
|
||||
{
|
||||
await connection.OpenAsync();
|
||||
|
||||
// Query per ottenere la struttura delle tabelle in SQL Server
|
||||
string sql = @"
|
||||
SELECT
|
||||
SCHEMA_NAME(t.schema_id) + '.' + t.name AS TableName,
|
||||
c.name AS ColumnName,
|
||||
tp.name AS DataType,
|
||||
c.max_length,
|
||||
c.precision,
|
||||
c.scale,
|
||||
c.is_nullable,
|
||||
CASE WHEN pk.column_id IS NOT NULL THEN 1 ELSE 0 END AS IsPrimaryKey,
|
||||
CASE WHEN fk.parent_column_id IS NOT NULL THEN 1 ELSE 0 END AS IsForeignKey,
|
||||
SCHEMA_NAME(ref_t.schema_id) + '.' + ref_t.name AS ReferencedTable,
|
||||
ref_c.name AS ReferencedColumn
|
||||
FROM
|
||||
sys.tables t
|
||||
INNER JOIN
|
||||
sys.columns c ON t.object_id = c.object_id
|
||||
INNER JOIN
|
||||
sys.types tp ON c.user_type_id = tp.user_type_id
|
||||
LEFT JOIN
|
||||
(SELECT
|
||||
ic.object_id,
|
||||
ic.column_id
|
||||
FROM
|
||||
sys.indexes i
|
||||
INNER JOIN
|
||||
sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
|
||||
WHERE
|
||||
i.is_primary_key = 1) pk ON t.object_id = pk.object_id AND c.column_id = pk.column_id
|
||||
LEFT JOIN
|
||||
sys.foreign_key_columns fk ON t.object_id = fk.parent_object_id AND c.column_id = fk.parent_column_id
|
||||
LEFT JOIN
|
||||
sys.tables ref_t ON fk.referenced_object_id = ref_t.object_id
|
||||
LEFT JOIN
|
||||
sys.columns ref_c ON fk.referenced_object_id = ref_c.object_id AND fk.referenced_column_id = ref_c.column_id
|
||||
WHERE
|
||||
t.is_ms_shipped = 0
|
||||
ORDER BY
|
||||
TableName, c.column_id";
|
||||
|
||||
using (var command = new SqlCommand(sql, connection))
|
||||
{
|
||||
command.CommandType = CommandType.Text;
|
||||
|
||||
using (var reader = await command.ExecuteReaderAsync())
|
||||
{
|
||||
string currentTable = null;
|
||||
List<DbColumnInfo> columns = null;
|
||||
|
||||
while (await reader.ReadAsync())
|
||||
{
|
||||
string tableName = reader.GetString(0);
|
||||
|
||||
// Se stiamo passando a una nuova tabella, aggiungiamo la tabella precedente e creiamo una nuova lista
|
||||
if (currentTable != tableName)
|
||||
{
|
||||
if (currentTable != null && columns != null && columns.Count > 0)
|
||||
{
|
||||
result[currentTable] = columns;
|
||||
}
|
||||
|
||||
currentTable = tableName;
|
||||
columns = new List<DbColumnInfo>();
|
||||
}
|
||||
|
||||
// Formato del tipo di dati con precisione e scala per tipi numerici o lunghezza per tipi stringa
|
||||
string dataType = reader.GetString(2);
|
||||
int maxLength = reader.GetInt16(3);
|
||||
byte precision = reader.GetByte(4);
|
||||
byte scale = reader.GetByte(5);
|
||||
|
||||
// Formattazione tipo di dati per SQL Server
|
||||
string formattedDataType = FormatSqlServerDataType(dataType, maxLength, precision, scale);
|
||||
|
||||
var columnInfo = new DbColumnInfo
|
||||
{
|
||||
Name = reader.GetString(1),
|
||||
DataType = formattedDataType,
|
||||
IsNullable = reader.GetBoolean(6),
|
||||
IsPrimaryKey = reader.GetInt32(7) == 1,
|
||||
IsForeignKey = reader.GetInt32(8) == 1,
|
||||
ReferencedTable = reader.IsDBNull(9) ? null : reader.GetString(9),
|
||||
ReferencedColumn = reader.IsDBNull(10) ? null : reader.GetString(10)
|
||||
};
|
||||
|
||||
columns?.Add(columnInfo);
|
||||
}
|
||||
|
||||
// Aggiungiamo l'ultima tabella
|
||||
if (currentTable != null && columns != null && columns.Count > 0)
|
||||
{
|
||||
result[currentTable] = columns;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"Errore nel recupero dello schema del database SQL Server: {ex.Message}");
|
||||
throw;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static string FormatSqlServerDataType(string dataType, int maxLength, byte precision, byte scale)
|
||||
{
|
||||
string formattedDataType = dataType;
|
||||
|
||||
if (dataType == "nvarchar" || dataType == "varchar" || dataType == "char" || dataType == "nchar")
|
||||
{
|
||||
if (maxLength == -1)
|
||||
formattedDataType += "(MAX)";
|
||||
else if (dataType.StartsWith("n")) // tipi Unicode - la lunghezza è in byte, dobbiamo dividerla per 2
|
||||
formattedDataType += $"({maxLength / 2})";
|
||||
else
|
||||
formattedDataType += $"({maxLength})";
|
||||
}
|
||||
else if (dataType == "decimal" || dataType == "numeric")
|
||||
{
|
||||
formattedDataType += $"({precision},{scale})";
|
||||
}
|
||||
|
||||
return formattedDataType;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user