DapperTransaction güncellemesi

Hatalar vermeye başladı. Listelerdeki veriler yüklenmeden yeni butonuna basınca hata aldım.
This commit is contained in:
Sedat Öztürk 2025-11-06 23:33:22 +03:00
parent 5e8ca56af1
commit 01db1c6b23
13 changed files with 513 additions and 291 deletions

View file

@ -1,6 +1,6 @@
{ {
"ConnectionStrings": { "ConnectionStrings": {
"SqlServer": "Server=sql;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;MultipleActiveResultSets=true;", "SqlServer": "Server=sql;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;",
"PostgreSql": "User ID=sa;Password=NvQp8s@l;Host=postgres;Port=5432;Database=Erp;" "PostgreSql": "User ID=sa;Password=NvQp8s@l;Host=postgres;Port=5432;Database=Erp;"
}, },
"Redis": { "Redis": {

View file

@ -1,6 +1,6 @@
{ {
"ConnectionStrings": { "ConnectionStrings": {
"SqlServer": "Server=sql;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;MultipleActiveResultSets=true;", "SqlServer": "Server=sql;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;",
"PostgreSql": "User ID=sa;Password=NvQp8s@l;Host=postgres;Port=5432;Database=Erp;" "PostgreSql": "User ID=sa;Password=NvQp8s@l;Host=postgres;Port=5432;Database=Erp;"
}, },
"Redis": { "Redis": {

View file

@ -1,7 +1,7 @@
{ {
"Seed": false, "Seed": false,
"ConnectionStrings": { "ConnectionStrings": {
"SqlServer": "Server=localhost;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;MultipleActiveResultSets=true;", "SqlServer": "Server=localhost;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;",
"PostgreSql": "User ID=sa;Password=NvQp8s@l;Host=localhost;Port=5432;Database=Erp;" "PostgreSql": "User ID=sa;Password=NvQp8s@l;Host=localhost;Port=5432;Database=Erp;"
}, },
"Redis": { "Redis": {

View file

@ -1,4 +1,5 @@
using System.Data; using System;
using System.Data;
using System.Data.Common; using System.Data.Common;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -25,43 +26,24 @@ public class DapperTransactionApi : ITransactionApi, ISupportsRollback
public async Task CommitAsync(CancellationToken cancellationToken = default) public async Task CommitAsync(CancellationToken cancellationToken = default)
{ {
// Check if transaction is still active
if (_isCompleted) if (_isCompleted)
{ {
return; // Already completed, nothing to do return;
} }
// Check if connection is still open and transaction is not disposed
if (DbTransaction?.Connection == null || DbTransaction.Connection.State != ConnectionState.Open) if (DbTransaction?.Connection == null || DbTransaction.Connection.State != ConnectionState.Open)
{ {
_isCompleted = true; _isCompleted = true;
return; // Connection closed or transaction disposed return;
} }
try try
{ {
await DbTransaction.CommitAsync(CancellationTokenProvider.FallbackToProvider(cancellationToken)); await DbTransaction.CommitAsync(CancellationTokenProvider.FallbackToProvider(cancellationToken));
_isCompleted = true;
} }
catch (System.InvalidOperationException) catch (InvalidOperationException)
{ {
// Transaction already completed or disposed // Transaction already completed or disposed
_isCompleted = true;
}
}
public void Dispose()
{
if (!_isCompleted && DbTransaction?.Connection != null)
{
try
{
// If not completed, rollback before disposing
DbTransaction?.Rollback();
}
catch
{
// Ignore rollback errors during disposal
} }
finally finally
{ {
@ -69,33 +51,51 @@ public class DapperTransactionApi : ITransactionApi, ISupportsRollback
} }
} }
DbTransaction?.Dispose(); public async Task RollbackAsync(CancellationToken cancellationToken = default)
}
public async Task RollbackAsync(CancellationToken cancellationToken)
{ {
// Check if transaction is still active
if (_isCompleted) if (_isCompleted)
{ {
return; // Already completed, nothing to do return;
} }
// Check if connection is still open and transaction is not disposed
if (DbTransaction?.Connection == null || DbTransaction.Connection.State != ConnectionState.Open) if (DbTransaction?.Connection == null || DbTransaction.Connection.State != ConnectionState.Open)
{ {
_isCompleted = true; _isCompleted = true;
return; // Connection closed or transaction disposed return;
} }
try try
{ {
await DbTransaction.RollbackAsync(CancellationTokenProvider.FallbackToProvider(cancellationToken)); await DbTransaction.RollbackAsync(CancellationTokenProvider.FallbackToProvider(cancellationToken));
_isCompleted = true;
} }
catch (System.InvalidOperationException) catch (InvalidOperationException)
{ {
// Transaction already completed or disposed // Transaction already completed or disposed
}
finally
{
_isCompleted = true; _isCompleted = true;
} }
} }
public void Dispose()
{
try
{
if (!_isCompleted && DbTransaction?.Connection?.State == ConnectionState.Open)
{
// UoW commit/rollback çağırmadıysa, dispose sırasında rollback dene
DbTransaction.Rollback();
}
}
catch
{
// Rollback sırasında hata olursa yut
}
finally
{
_isCompleted = true;
DbTransaction?.Dispose();
}
}
} }

View file

@ -15,118 +15,55 @@ namespace Kurs.Platform.Domain.DynamicData;
[ExposeKeyedService<IDynamicDataRepository>("Ms")] [ExposeKeyedService<IDynamicDataRepository>("Ms")]
public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency, IUnitOfWorkEnabled, IDisposable public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency, IUnitOfWorkEnabled, IDisposable
{ {
private readonly IUnitOfWorkManager unitOfWorkManager; private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly ICancellationTokenProvider cancellationTokenProvider; private readonly ICancellationTokenProvider _cancellationTokenProvider;
private readonly Dictionary<string, DbTransaction> transactions;
private readonly Dictionary<string, SqlConnection> connections; private readonly Dictionary<string, DbTransaction> _transactions;
private readonly HashSet<string> registeredTransactions; // Track registered transactions private readonly Dictionary<string, SqlConnection> _connections;
private readonly HashSet<string> _registeredTransactions;
private readonly HashSet<string> _registeredConnections;
private readonly object _lock = new object(); private readonly object _lock = new object();
public bool IsDisposed { get; private set; } public bool IsDisposed { get; private set; }
public MsDynamicDataRepository( public MsDynamicDataRepository(
IUnitOfWorkManager unitOfWorkManager, IUnitOfWorkManager unitOfWorkManager,
ICancellationTokenProvider cancellationTokenProvider) ICancellationTokenProvider cancellationTokenProvider)
{ {
this.unitOfWorkManager = unitOfWorkManager; _unitOfWorkManager = unitOfWorkManager;
this.cancellationTokenProvider = cancellationTokenProvider; _cancellationTokenProvider = cancellationTokenProvider;
transactions = new Dictionary<string, DbTransaction>();
connections = new Dictionary<string, SqlConnection>(); _transactions = [];
registeredTransactions = new HashSet<string>(); _connections = [];
_registeredTransactions = [];
_registeredConnections = [];
} }
private async Task<DbTransaction> GetOrCreateTransactionAsync(SqlConnection con) private string BuildKey(string cs)
{ {
var key = $"Dapper_{con.ConnectionString}"; var uowId = _unitOfWorkManager.Current?.GetHashCode() ?? 0;
return $"Dapper_{uowId}_{cs}";
lock (_lock)
{
// Check if we have a valid transaction for this connection
if (transactions.TryGetValue(key, out var transaction))
{
// Validate transaction is still usable
if (transaction?.Connection != null &&
transaction.Connection == con &&
transaction.Connection.State == ConnectionState.Open)
{
return transaction;
}
// Invalid transaction, remove it
try { transaction?.Dispose(); } catch { }
transactions.Remove(key);
}
}
// Ensure connection is open
if (con.State != ConnectionState.Open)
{
await con.OpenAsync();
}
// Create new transaction
var newTransaction = await con.BeginTransactionAsync();
bool shouldRegister = false;
lock (_lock)
{
transactions[key] = newTransaction;
// Only register with UnitOfWork once per transaction key
if (!registeredTransactions.Contains(key))
{
registeredTransactions.Add(key);
shouldRegister = true;
}
}
// Register with UnitOfWork if available and not already registered
if (shouldRegister && unitOfWorkManager.Current != null)
{
unitOfWorkManager.Current.AddTransactionApi(key, new DapperTransactionApi(newTransaction, cancellationTokenProvider));
unitOfWorkManager.Current.OnCompleted(() =>
{
lock (_lock)
{
transactions.Remove(key);
registeredTransactions.Remove(key);
}
return Task.CompletedTask;
});
}
return newTransaction;
} }
private async Task<SqlConnection> GetOrCreateConnectionAsync(string cs) private async Task<SqlConnection> GetOrCreateConnectionAsync(string cs)
{ {
var key = BuildKey(cs);
SqlConnection connection; SqlConnection connection;
var key = $"Dapper_{cs}";
lock (_lock) lock (_lock)
{ {
// Check if we have an existing connection if (_connections.TryGetValue(key, out connection))
if (connections.TryGetValue(key, out connection))
{ {
// Connection exists, check its state // varsa aynı connection'ı kullan
if (connection.State == ConnectionState.Open)
{
return connection;
}
// Connection is not open, will handle outside lock
} }
else else
{ {
// Create new connection
connection = new SqlConnection(cs); connection = new SqlConnection(cs);
connections[key] = connection; _connections[key] = connection;
} }
} }
// Handle connection state outside of lock // Lock dışında state yönetimi
try
{
if (connection.State == ConnectionState.Broken) if (connection.State == ConnectionState.Broken)
{ {
connection.Close(); connection.Close();
@ -134,37 +71,29 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency
if (connection.State == ConnectionState.Closed) if (connection.State == ConnectionState.Closed)
{ {
await connection.OpenAsync(); await connection.OpenAsync(_cancellationTokenProvider.FallbackToProvider(default));
}
}
catch
{
// If connection failed, create a new one
lock (_lock)
{
connections.Remove(key);
} }
connection = new SqlConnection(cs); // UoW tamamlandığında connection'ı kapatmak için tek seferlik kayıt
if (_unitOfWorkManager.Current != null)
{
lock (_lock) lock (_lock)
{ {
connections[key] = connection; if (!_registeredConnections.Contains(key))
}
await connection.OpenAsync();
}
// Register cleanup on UnitOfWork completion (only once)
if (unitOfWorkManager.Current != null)
{ {
unitOfWorkManager.Current.OnCompleted(async () => _registeredConnections.Add(key);
_unitOfWorkManager.Current.OnCompleted(async () =>
{ {
SqlConnection conn = null; SqlConnection conn = null;
lock (_lock) lock (_lock)
{ {
if (connections.TryGetValue(key, out conn)) if (_connections.TryGetValue(key, out conn))
{ {
connections.Remove(key); _connections.Remove(key);
} }
_registeredConnections.Remove(key);
} }
if (conn != null) if (conn != null)
@ -177,27 +106,92 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency
} }
conn.Dispose(); conn.Dispose();
} }
catch { } catch
{
// ignore
}
} }
}); });
} }
}
}
return connection; return connection;
} }
private async Task<DbTransaction> GetOrCreateTransactionAsync(SqlConnection con, string cs)
{
var key = BuildKey(cs);
lock (_lock)
{
if (_transactions.TryGetValue(key, out var existing))
{
if (existing?.Connection != null &&
existing.Connection == con &&
existing.Connection.State == ConnectionState.Open)
{
return existing;
}
try { existing?.Dispose(); } catch { }
_transactions.Remove(key);
}
}
var newTransaction = await con.BeginTransactionAsync(_cancellationTokenProvider.FallbackToProvider(default));
bool shouldRegister = false;
lock (_lock)
{
_transactions[key] = newTransaction;
if (!_registeredTransactions.Contains(key))
{
_registeredTransactions.Add(key);
shouldRegister = true;
}
}
if (shouldRegister && _unitOfWorkManager.Current != null)
{
_unitOfWorkManager.Current.AddTransactionApi(
key,
new DapperTransactionApi(newTransaction, _cancellationTokenProvider)
);
_unitOfWorkManager.Current.OnCompleted(() =>
{
lock (_lock)
{
_transactions.Remove(key);
_registeredTransactions.Remove(key);
}
return Task.CompletedTask;
});
}
return newTransaction;
}
// ------------------ Dapper metotları ------------------
public virtual async Task<List<T>> QueryAsync<T>(string sql, string cs, Dictionary<string, object> parameters = null) public virtual async Task<List<T>> QueryAsync<T>(string sql, string cs, Dictionary<string, object> parameters = null)
{ {
var param = new DynamicParameters(parameters); var param = new DynamicParameters(parameters);
var dbConnection = await GetOrCreateConnectionAsync(cs); var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection); var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
return (await dbConnection.QueryAsync<T>(sql, param, transaction)).AsList();
var result = await dbConnection.QueryAsync<T>(sql, param, transaction);
return result.AsList(); // buffered => reader kapanır, MARS gerekmez
} }
public virtual async Task<IEnumerable<dynamic>> QueryAsync(string sql, string cs, Dictionary<string, object> parameters = null) public virtual async Task<IEnumerable<dynamic>> QueryAsync(string sql, string cs, Dictionary<string, object> parameters = null)
{ {
var param = new DynamicParameters(parameters); var param = new DynamicParameters(parameters);
var dbConnection = await GetOrCreateConnectionAsync(cs); var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection); var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
return await dbConnection.QueryAsync(sql, param, transaction); return await dbConnection.QueryAsync(sql, param, transaction);
} }
@ -205,7 +199,8 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency
{ {
var param = new DynamicParameters(parameters); var param = new DynamicParameters(parameters);
var dbConnection = await GetOrCreateConnectionAsync(cs); var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection); var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
return await dbConnection.QuerySingleAsync<T>(sql, param, transaction); return await dbConnection.QuerySingleAsync<T>(sql, param, transaction);
} }
@ -213,25 +208,30 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency
{ {
var param = new DynamicParameters(parameters); var param = new DynamicParameters(parameters);
var dbConnection = await GetOrCreateConnectionAsync(cs); var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection); var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
return await dbConnection.ExecuteScalarAsync<T>(sql, param, transaction); return await dbConnection.ExecuteScalarAsync<T>(sql, param, transaction);
} }
public virtual async Task<T> GetSingleField<T>(string cs, string tableName, string id, string selectFieldName, string idFieldName = "Id") public virtual async Task<T> GetSingleField<T>(string cs, string tableName, string id, string selectFieldName, string idFieldName = "Id")
{ {
var param = new { ID = id }; var param = new { ID = id };
string sql = $"select {selectFieldName} from {tableName} where {idFieldName} = @ID "; var sql = $"select {selectFieldName} from {tableName} where {idFieldName} = @ID ";
var dbConnection = await GetOrCreateConnectionAsync(cs); var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection); var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
return await dbConnection.QueryFirstAsync<T>(sql, param, transaction); return await dbConnection.QueryFirstAsync<T>(sql, param, transaction);
} }
public virtual async Task<int> UpdateField(string cs, string tableName, string id, string updateFieldName, string value, string idFieldName = "Id") public virtual async Task<int> UpdateField(string cs, string tableName, string id, string updateFieldName, string value, string idFieldName = "Id")
{ {
var param = new { ID = id }; var param = new { ID = id };
string sql = $"update {tableName} set {updateFieldName} = '{value}' where Id = @ID "; var sql = $"update {tableName} set {updateFieldName} = '{value}' where {idFieldName} = @ID ";
var dbConnection = await GetOrCreateConnectionAsync(cs); var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection); var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
return await dbConnection.ExecuteAsync(sql, param, transaction); return await dbConnection.ExecuteAsync(sql, param, transaction);
} }
@ -239,10 +239,13 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency
{ {
var param = new DynamicParameters(parameters); var param = new DynamicParameters(parameters);
var dbConnection = await GetOrCreateConnectionAsync(cs); var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection); var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
return await dbConnection.ExecuteAsync(sql, param, transaction); return await dbConnection.ExecuteAsync(sql, param, transaction);
} }
// ------------------ Dispose ------------------
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);
@ -251,49 +254,42 @@ public class MsDynamicDataRepository : IDynamicDataRepository, IScopedDependency
private void Dispose(bool disposing) private void Dispose(bool disposing)
{ {
if (!IsDisposed) if (IsDisposed)
{ {
return;
}
if (disposing) if (disposing)
{ {
lock (_lock) lock (_lock)
{ {
// Dispose transactions first foreach (var tx in _transactions.Values)
foreach (var transaction in transactions.Values)
{ {
try try { tx?.Dispose(); } catch { }
{
transaction?.Dispose();
} }
catch _transactions.Clear();
{ _registeredTransactions.Clear();
// Ignore disposal errors
}
}
transactions.Clear();
// Then dispose connections foreach (var conn in _connections.Values)
foreach (var connection in connections.Values)
{ {
try try
{ {
if (connection != null) if (conn != null)
{ {
if (connection.State == ConnectionState.Open) if (conn.State != ConnectionState.Closed)
{ {
connection.Close(); conn.Close();
} }
connection.Dispose(); conn.Dispose();
} }
} }
catch catch { }
{ }
// Ignore disposal errors _connections.Clear();
} _registeredConnections.Clear();
}
connections.Clear();
} }
} }
IsDisposed = true; IsDisposed = true;
} }
}
} }

View file

@ -15,69 +15,130 @@ namespace Kurs.Platform.Domain.DynamicData;
[ExposeKeyedService<IDynamicDataRepository>("Pg")] [ExposeKeyedService<IDynamicDataRepository>("Pg")]
public class PgDynamicDataRepository : IDynamicDataRepository, IScopedDependency, IUnitOfWorkEnabled, IDisposable public class PgDynamicDataRepository : IDynamicDataRepository, IScopedDependency, IUnitOfWorkEnabled, IDisposable
{ {
private readonly IUnitOfWorkManager unitOfWorkManager; private readonly IUnitOfWorkManager _unitOfWorkManager;
private readonly ICancellationTokenProvider cancellationTokenProvider; private readonly ICancellationTokenProvider _cancellationTokenProvider;
private Dictionary<string, DbTransaction> transactions;
private Dictionary<string, NpgsqlConnection> connections; private readonly Dictionary<string, DbTransaction> _transactions;
private readonly Dictionary<string, NpgsqlConnection> _connections;
public bool IsDisposed { get; private set; } public bool IsDisposed { get; private set; }
public PgDynamicDataRepository( public PgDynamicDataRepository(
IUnitOfWorkManager unitOfWorkManager, IUnitOfWorkManager unitOfWorkManager,
ICancellationTokenProvider cancellationTokenProvider) ICancellationTokenProvider cancellationTokenProvider)
{ {
this.unitOfWorkManager = unitOfWorkManager; _unitOfWorkManager = unitOfWorkManager;
this.cancellationTokenProvider = cancellationTokenProvider; _cancellationTokenProvider = cancellationTokenProvider;
transactions = []; _transactions = [];
connections = []; _connections = [];
} }
private async Task<DbTransaction> GetOrCreateTransactionAsync(NpgsqlConnection con) private string BuildKey(string cs)
{ {
var key = $"Dapper_{con.ConnectionString}"; var uowId = _unitOfWorkManager.Current?.GetHashCode() ?? 0;
var transaction = transactions.GetOrDefault(key); return $"Dapper_{uowId}_{cs}";
if (transaction == null || transaction.Connection == null)
{
transaction = await con.BeginTransactionAsync();
unitOfWorkManager.Current.AddTransactionApi(key, new DapperTransactionApi(transaction, cancellationTokenProvider));
transactions.Add(key, transaction);
unitOfWorkManager.Current.OnCompleted(() =>
{
transaction = null;
return Task.CompletedTask;
});
}
return transaction;
} }
private async Task<NpgsqlConnection> GetOrCreateConnectionAsync(string cs) private async Task<NpgsqlConnection> GetOrCreateConnectionAsync(string cs)
{ {
var key = $"Dapper_{cs}"; var key = BuildKey(cs);
var connection = connections.GetOrDefault(key); if (!_connections.TryGetValue(key, out var connection))
if (connection == null)
{ {
connection = new NpgsqlConnection(cs); connection = new NpgsqlConnection(cs);
connections.Add(key, connection); _connections[key] = connection;
} }
if (connection.State == ConnectionState.Broken)
{
connection.Close();
}
if (connection.State != ConnectionState.Open) if (connection.State != ConnectionState.Open)
{ {
await connection.OpenAsync(); await connection.OpenAsync(_cancellationTokenProvider.FallbackToProvider(default));
} }
if (_unitOfWorkManager.Current != null)
{
_unitOfWorkManager.Current.OnCompleted(async () =>
{
if (_connections.TryGetValue(key, out var conn))
{
_connections.Remove(key);
try
{
if (conn.State != ConnectionState.Closed)
{
await conn.CloseAsync();
}
conn.Dispose();
}
catch
{
// ignore
}
}
});
}
return connection; return connection;
} }
private async Task<DbTransaction> GetOrCreateTransactionAsync(NpgsqlConnection con, string cs)
{
var key = BuildKey(cs);
if (_transactions.TryGetValue(key, out var tx))
{
if (tx?.Connection != null &&
tx.Connection == con &&
tx.Connection.State == ConnectionState.Open)
{
return tx;
}
try { tx?.Dispose(); } catch { }
_transactions.Remove(key);
}
var newTx = await con.BeginTransactionAsync(_cancellationTokenProvider.FallbackToProvider(default));
_transactions[key] = newTx;
if (_unitOfWorkManager.Current != null)
{
_unitOfWorkManager.Current.AddTransactionApi(
key,
new DapperTransactionApi(newTx, _cancellationTokenProvider)
);
_unitOfWorkManager.Current.OnCompleted(() =>
{
_transactions.Remove(key);
return Task.CompletedTask;
});
}
return newTx;
}
// ------------------ Dapper metotları ------------------
public virtual async Task<List<T>> QueryAsync<T>(string sql, string cs, Dictionary<string, object> parameters = null) public virtual async Task<List<T>> QueryAsync<T>(string sql, string cs, Dictionary<string, object> parameters = null)
{ {
var param = new DynamicParameters(parameters); var param = new DynamicParameters(parameters);
var dbConnection = await GetOrCreateConnectionAsync(cs); var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection); var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
return (await dbConnection.QueryAsync<T>(sql, param, transaction)).AsList();
var result = await dbConnection.QueryAsync<T>(sql, param, transaction);
return result.AsList();
} }
public virtual async Task<IEnumerable<dynamic>> QueryAsync(string sql, string cs, Dictionary<string, object> parameters = null) public virtual async Task<IEnumerable<dynamic>> QueryAsync(string sql, string cs, Dictionary<string, object> parameters = null)
{ {
var param = new DynamicParameters(parameters); var param = new DynamicParameters(parameters);
var dbConnection = await GetOrCreateConnectionAsync(cs); var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection); var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
return await dbConnection.QueryAsync(sql, param, transaction); return await dbConnection.QueryAsync(sql, param, transaction);
} }
@ -85,7 +146,8 @@ public class PgDynamicDataRepository : IDynamicDataRepository, IScopedDependency
{ {
var param = new DynamicParameters(parameters); var param = new DynamicParameters(parameters);
var dbConnection = await GetOrCreateConnectionAsync(cs); var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection); var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
return await dbConnection.QuerySingleAsync<T>(sql, param, transaction); return await dbConnection.QuerySingleAsync<T>(sql, param, transaction);
} }
@ -93,25 +155,30 @@ public class PgDynamicDataRepository : IDynamicDataRepository, IScopedDependency
{ {
var param = new DynamicParameters(parameters); var param = new DynamicParameters(parameters);
var dbConnection = await GetOrCreateConnectionAsync(cs); var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection); var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
return await dbConnection.ExecuteScalarAsync<T>(sql, param, transaction); return await dbConnection.ExecuteScalarAsync<T>(sql, param, transaction);
} }
public virtual async Task<T> GetSingleField<T>(string cs, string tableName, string id, string selectFieldName, string idFieldName = "Id") public virtual async Task<T> GetSingleField<T>(string cs, string tableName, string id, string selectFieldName, string idFieldName = "Id")
{ {
var param = new { ID = id }; var param = new { ID = id };
string sql = $"select {selectFieldName} from {tableName} where {idFieldName} = @ID "; var sql = $"select {selectFieldName} from {tableName} where {idFieldName} = @ID ";
var dbConnection = await GetOrCreateConnectionAsync(cs); var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection); var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
return await dbConnection.QueryFirstAsync<T>(sql, param, transaction); return await dbConnection.QueryFirstAsync<T>(sql, param, transaction);
} }
public virtual async Task<int> UpdateField(string cs, string tableName, string id, string updateFieldName, string value, string idFieldName = "Id") public virtual async Task<int> UpdateField(string cs, string tableName, string id, string updateFieldName, string value, string idFieldName = "Id")
{ {
var param = new { ID = id }; var param = new { ID = id };
string sql = $"update {tableName} set {updateFieldName} = '{value}' where Id = @ID "; var sql = $"update {tableName} set {updateFieldName} = '{value}' where {idFieldName} = @ID ";
var dbConnection = await GetOrCreateConnectionAsync(cs); var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection); var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
return await dbConnection.ExecuteAsync(sql, param, transaction); return await dbConnection.ExecuteAsync(sql, param, transaction);
} }
@ -119,10 +186,13 @@ public class PgDynamicDataRepository : IDynamicDataRepository, IScopedDependency
{ {
var param = new DynamicParameters(parameters); var param = new DynamicParameters(parameters);
var dbConnection = await GetOrCreateConnectionAsync(cs); var dbConnection = await GetOrCreateConnectionAsync(cs);
var transaction = await GetOrCreateTransactionAsync(dbConnection); var transaction = await GetOrCreateTransactionAsync(dbConnection, cs);
return await dbConnection.ExecuteAsync(sql, param, transaction); return await dbConnection.ExecuteAsync(sql, param, transaction);
} }
// ------------------ Dispose ------------------
public void Dispose() public void Dispose()
{ {
Dispose(true); Dispose(true);
@ -131,23 +201,40 @@ public class PgDynamicDataRepository : IDynamicDataRepository, IScopedDependency
private void Dispose(bool disposing) private void Dispose(bool disposing)
{ {
if (!IsDisposed) if (IsDisposed)
{ {
return;
}
if (disposing) if (disposing)
{ {
foreach (var connection in connections.Values) foreach (var tx in _transactions.Values)
{
try { tx?.Dispose(); } catch { }
}
_transactions.Clear();
foreach (var connection in _connections.Values)
{
try
{ {
if (connection != null) if (connection != null)
{ {
if (connection.State == ConnectionState.Open) if (connection.State != ConnectionState.Closed)
{ {
connection.Close(); connection.Close();
} }
connection.Dispose(); connection.Dispose();
} }
} }
catch
{
// ignore
} }
}
_connections.Clear();
}
IsDisposed = true; IsDisposed = true;
} }
}
} }

View file

@ -100,7 +100,9 @@
"props": null, "props": null,
"description": null, "description": null,
"isActive": true, "isActive": true,
"dependencies": ["DynamicEntityComponent"] "dependencies": [
"DynamicEntityComponent"
]
} }
], ],
"ReportCategories": [ "ReportCategories": [
@ -2406,7 +2408,12 @@
"minSalary": 80000, "minSalary": 80000,
"maxSalary": 120000, "maxSalary": 120000,
"currencyCode": "USD", "currencyCode": "USD",
"requiredSkills": ["JavaScript", "TypeScript", "React", "Node.js"], "requiredSkills": [
"JavaScript",
"TypeScript",
"React",
"Node.js"
],
"responsibilities": [ "responsibilities": [
"Develop frontend and backend applications", "Develop frontend and backend applications",
"Write clean and maintainable code", "Write clean and maintainable code",
@ -4101,7 +4108,9 @@
{ {
"postContent": "CI/CD pipeline güncellememiz tamamlandı! Deployment süremiz %40 azaldı. Otomasyonun gücü 💪", "postContent": "CI/CD pipeline güncellememiz tamamlandı! Deployment süremiz %40 azaldı. Otomasyonun gücü 💪",
"type": "video", "type": "video",
"urls": ["https://www.w3schools.com/html/mov_bbb.mp4"] "urls": [
"https://www.w3schools.com/html/mov_bbb.mp4"
]
} }
], ],
"SocialPollOptions": [ "SocialPollOptions": [
@ -4250,11 +4259,88 @@
"parentGroupCode": "METAL", "parentGroupCode": "METAL",
"isActive": true "isActive": true
}, },
{ {
"code": "YARI-MAMUL", "code": "YARI-MAMUL",
"name": "Yarı Mamuller", "name": "Yarı Mamuller",
"parentGroupCode": "MAMUL", "parentGroupCode": "MAMUL",
"isActive": true "isActive": true
} }
],
"Materials": [
{
"code": "MT001",
"name": "Yüksek Kaliteli Çelik Levha 10mm",
"barcode": "1234567890123",
"description": "Çelik Levha 2mm",
"materialTypeCode": "RAW",
"materialGroupCode": "METAL",
"uomName": "kg",
"costPrice": 15.5,
"salesPrice": 18.75,
"currencyCode": "TRY",
"isActive": true,
"totalStock": 2500.0,
"trackingType": "Quantity"
},
{
"code": "MT002",
"name": "Alüminyum Profil 40x40",
"barcode": "1234567890124",
"description": "Alüminyum Profil 40x40",
"materialTypeCode": "SEMI",
"materialGroupCode": "METAL",
"uomName": "Adet",
"costPrice": 45.0,
"salesPrice": 55.0,
"currencyCode": "TRY",
"isActive": true,
"totalStock": 1200.0,
"trackingType": "Lot"
},
{
"code": "PR001",
"name": "Montajlı Motor Grubu A-Type",
"barcode": "1234567890125",
"description": "Motor Grubu A-Type",
"materialTypeCode": "FINISHED",
"materialGroupCode": "MOTOR",
"uomName": "Adet",
"costPrice": 850.0,
"salesPrice": 1200.0,
"currencyCode": "TRY",
"isActive": true,
"totalStock": 45.0,
"trackingType": "Lot"
},
{
"code": "SF001",
"name": "Kesme Yağı Premium",
"barcode": "1234567890126",
"description": "Kesme Yağı Premium",
"materialTypeCode": "FINISHED",
"materialGroupCode": "PLASTIK",
"uomName": "Adet",
"costPrice": 25.0,
"salesPrice": 35.0,
"currencyCode": "TRY",
"isActive": true,
"totalStock": 150.0,
"trackingType": "Quantity"
},
{
"code": "PK001",
"name": "Plastik Kapak Komponenti",
"barcode": "1234567890127",
"description": "Plastik Kapak",
"materialTypeCode": "SEMI",
"materialGroupCode": "KIMYA",
"uomName": "Adet",
"costPrice": 8.5,
"salesPrice": 15.0,
"currencyCode": "TRY",
"isActive": true,
"totalStock": 850.0,
"trackingType": "Serial"
}
] ]
} }

View file

@ -94,6 +94,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
private readonly IRepository<SocialLike, Guid> _socialLikeRepository; private readonly IRepository<SocialLike, Guid> _socialLikeRepository;
private readonly IRepository<MaterialType, Guid> _materialTypeRepository; private readonly IRepository<MaterialType, Guid> _materialTypeRepository;
private readonly IRepository<MaterialGroup, Guid> _materialGroupRepository; private readonly IRepository<MaterialGroup, Guid> _materialGroupRepository;
private readonly IRepository<Material, Guid> _materialRepository;
public TenantDataSeeder( public TenantDataSeeder(
IClock clock, IClock clock,
@ -170,7 +171,8 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
IRepository<SocialComment, Guid> socialCommentRepository, IRepository<SocialComment, Guid> socialCommentRepository,
IRepository<SocialLike, Guid> socialLikeRepository, IRepository<SocialLike, Guid> socialLikeRepository,
IRepository<MaterialType, Guid> materialTypeRepository, IRepository<MaterialType, Guid> materialTypeRepository,
IRepository<MaterialGroup, Guid> materialGroupRepository IRepository<MaterialGroup, Guid> materialGroupRepository,
IRepository<Material, Guid> materialRepository
) )
{ {
_clock = clock; _clock = clock;
@ -249,6 +251,7 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
_socialLikeRepository = socialLikeRepository; _socialLikeRepository = socialLikeRepository;
_materialTypeRepository = materialTypeRepository; _materialTypeRepository = materialTypeRepository;
_materialGroupRepository = materialGroupRepository; _materialGroupRepository = materialGroupRepository;
_materialRepository = materialRepository;
} }
private static IConfigurationRoot BuildConfiguration() private static IConfigurationRoot BuildConfiguration()
@ -1641,5 +1644,37 @@ public class TenantDataSeeder : IDataSeedContributor, ITransientDependency
}, autoSave: true); }, autoSave: true);
} }
} }
foreach (var item in items.Materials)
{
if (string.IsNullOrWhiteSpace(item.Code))
continue;
var exists = await _materialRepository.AnyAsync(x => x.Code == item.Code);
if (exists)
continue;
var type = await _materialTypeRepository.FirstOrDefaultAsync(x => x.Code == item.MaterialTypeCode);
var group = await _materialGroupRepository.FirstOrDefaultAsync(x => x.Code == item.MaterialGroupCode);
var currency = await _currencyRepository.FirstOrDefaultAsync(x => x.Code == item.CurrencyCode);
var uom = await _uomRepository.FirstOrDefaultAsync(x => x.Name == item.UomName);
await _materialRepository.InsertAsync(new Material
{
Code = item.Code,
Name = item.Name,
Description = item.Description,
UomId = uom?.Id,
CostPrice = item.CostPrice,
SalesPrice = item.SalesPrice,
CurrencyId = currency?.Id,
IsActive = item.IsActive,
TotalStock = item.TotalStock,
MaterialTypeId = type?.Id,
MaterialGroupId = group?.Id,
Barcode = item.Barcode,
TrackingType = item.TrackingType
}, autoSave: true);
}
} }
} }

View file

@ -52,10 +52,6 @@ public class TenantSeederDto
public List<InterestingSeedDto> Interesting { get; set; } public List<InterestingSeedDto> Interesting { get; set; }
public List<ProgramSeedDto> Programs { get; set; } public List<ProgramSeedDto> Programs { get; set; }
//Supply Chain
public List<MaterialTypeSeedDto> MaterialTypes { get; set; }
public List<MaterialGroupSeedDto> MaterialGroups { get; set; }
//Hr //Hr
public List<EmployeeTypeSeedDto> EmployeeTypes { get; set; } public List<EmployeeTypeSeedDto> EmployeeTypes { get; set; }
public List<JobPositionSeedDto> JobPositions { get; set; } public List<JobPositionSeedDto> JobPositions { get; set; }
@ -83,6 +79,28 @@ public class TenantSeederDto
public List<SocialPollOptionSeedDto> SocialPollOptions { get; set; } public List<SocialPollOptionSeedDto> SocialPollOptions { get; set; }
public List<SocialCommentSeedDto> SocialComments { get; set; } public List<SocialCommentSeedDto> SocialComments { get; set; }
public List<SocialLikeSeedDto> SocialLikes { get; set; } public List<SocialLikeSeedDto> SocialLikes { get; set; }
//Supply Chain
public List<MaterialTypeSeedDto> MaterialTypes { get; set; }
public List<MaterialGroupSeedDto> MaterialGroups { get; set; }
public List<MaterialSeedDto> Materials { get; set; }
}
public class MaterialSeedDto
{
public string Code { get; set; }
public string Name { get; set; }
public string Barcode { get; set; }
public string Description { get; set; }
public string MaterialTypeCode { get; set; }
public string MaterialGroupCode { get; set; }
public string UomName { get; set; }
public decimal CostPrice { get; set; }
public decimal SalesPrice { get; set; }
public string CurrencyCode { get; set; }
public bool IsActive { get; set; }
public decimal TotalStock { get; set; }
public string TrackingType { get; set; }
} }
public class MaterialTypeSeedDto public class MaterialTypeSeedDto

View file

@ -9,7 +9,7 @@
"BaseDomain": "sozsoft.com" "BaseDomain": "sozsoft.com"
}, },
"ConnectionStrings": { "ConnectionStrings": {
"SqlServer": "Server=sql;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;MultipleActiveResultSets=true;", "SqlServer": "Server=sql;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;",
"PostgreSql": "User ID=sa;Password=NvQp8s@l;Host=postgres;Port=5432;Database=Erp;" "PostgreSql": "User ID=sa;Password=NvQp8s@l;Host=postgres;Port=5432;Database=Erp;"
}, },
"Redis": { "Redis": {

View file

@ -9,7 +9,7 @@
"BaseDomain": "sozsoft.com" "BaseDomain": "sozsoft.com"
}, },
"ConnectionStrings": { "ConnectionStrings": {
"SqlServer": "Server=sql;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;MultipleActiveResultSets=true;", "SqlServer": "Server=sql;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;",
"PostgreSql": "User ID=sa;Password=NvQp8s@l;Host=postgres;Port=5432;Database=Erp;" "PostgreSql": "User ID=sa;Password=NvQp8s@l;Host=postgres;Port=5432;Database=Erp;"
}, },
"Redis": { "Redis": {

View file

@ -9,7 +9,7 @@
"Version": "1.0.1" "Version": "1.0.1"
}, },
"ConnectionStrings": { "ConnectionStrings": {
"SqlServer": "Server=localhost;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;MultipleActiveResultSets=true;", "SqlServer": "Server=localhost;Database=Erp;User Id=sa;password=NvQp8s@l;Trusted_Connection=False;Encrypt=False;TrustServerCertificate=True;Connection Timeout=60;",
"PostgreSql": "User ID=sa;Password=NvQp8s@l;Host=localhost;Port=5432;Database=Erp;" "PostgreSql": "User ID=sa;Password=NvQp8s@l;Host=localhost;Port=5432;Database=Erp;"
}, },
"Redis": { "Redis": {

View file

@ -279,7 +279,7 @@ function TenantConnectionString({
'value', 'value',
'Server=sql;Database=' + 'Server=sql;Database=' +
name + name +
';User Id=sa;password=@Password;Trusted_Connection=False;TrustServerCertificate=True;Connection Timeout=60;MultipleActiveResultSets=true;', ';User Id=sa;password=@Password;Trusted_Connection=False;TrustServerCertificate=True;Connection Timeout=60;',
) )
else if (option?.value == 2) else if (option?.value == 2)
//MsSql //MsSql