Kit.Core/LibCommon/Kit.Core.Helpers/Db/SqlHelperPostgres.cs

264 lines
9.7 KiB
C#

using Npgsql;
using Npgsql.NameTranslation;
using System.Data;
using System.Text;
namespace Kit.Helpers.Postgres;
public class SqlHelper : ISqlHelper
{
private SqlHelper() { }
public INpgsqlNameTranslator Translator { get; } = new NpgsqlSnakeCaseNameTranslator();
public static SqlHelper Instance { get; } = new SqlHelper();
private IEnumerable<KeyValuePair<string, object>>? UpdateParamNames(IEnumerable<KeyValuePair<string, object>>? @params)
{
var result = new List<KeyValuePair<string, object>>();
if (@params.IsNullOrEmpty()) return result;
@params.ForEach(_param =>
{
string key = "_" + Translator.TranslateMemberName(_param.Key.Remove(" ")!.Trim('@', '_'));
result.Add(new KeyValuePair<string, object>(key, _param.Value));
});
return result;
}
private string GenerateParamsForQuery(IEnumerable<KeyValuePair<string, object>>? @params)
{
var sb = new StringBuilder();
if (@params.IsNullOrEmpty() == false)
{
for (int i = 0; i < @params.Count(); i++)
{
string key = @params.ElementAt(i).Key;
sb.Append(key).Append(" => :").Append(key);
if (i < @params.Count() - 1)
{
sb.Append(", ");
}
}
}
return sb.ToString();
}
private string TranslateName(string name, bool withStrictSyntax)
{
if (withStrictSyntax)
{
return name;
}
return name.Remove("[")!.Remove("]")!.Split('.').Select(x => Translator.TranslateMemberName(x)).Join(".");
}
private NpgsqlCommand AppendTypes(NpgsqlCommand command, RequestParams requestParams)
{
requestParams.CustomTypes?.ForEach(x =>
{
command.Connection?.TypeMapper.MapComposite(x.Type, TranslateName(x.Name, requestParams.WithStrictSyntax), Translator);
});
return command;
}
private NpgsqlCommand CreateCommand(RequestParams requestParams)
{
string sql = TranslateName(requestParams.CommandText, requestParams.WithStrictSyntax);
bool hasParameters = requestParams.Parameters != null && requestParams.Parameters.Count() > 0;
var parameters = UpdateParamNames(requestParams.Parameters);
if (requestParams.ConnectionString.Value.IsNullOrEmpty()) throw new ArgumentNullException("connectionString");
if (sql.IsNullOrEmpty()) throw new ArgumentNullException("sql");
var connection = new NpgsqlConnection(requestParams.ConnectionString.Value);
NpgsqlCommand command = connection.CreateCommand();
command.CommandTimeout = connection.ConnectionTimeout;
if (requestParams.IsStoredProcedure)
{
sql = $"select * from {sql}({GenerateParamsForQuery(parameters)});";
}
command.CommandType = System.Data.CommandType.Text;
command.CommandText = sql;
if (parameters != null && parameters.Any())
{
foreach (var keyValuePair in parameters)
{
string key = keyValuePair.Key.Remove("@").Remove(" ").ToLower();
if (keyValuePair.Value is Guid)
{
command.Parameters.AddWithValue(key.ToLower(), keyValuePair.Value ?? Guid.Empty);
}
else
{
command.Parameters.AddWithValue(key.ToLower(), keyValuePair.Value ?? DBNull.Value);
}
}
}
return command;
}
public void ExecuteNonQuery(RequestParams requestParams)
{
NpgsqlCommand command = CreateCommand(requestParams);
using (command.Connection)
{
command.Connection.Open();
AppendTypes(command, requestParams);
command.ExecuteNonQuery();
command.Connection.Close();
}
}
public object ExecuteScalar(RequestParams requestParams)
{
object returnScalar;
NpgsqlCommand command = CreateCommand(requestParams);
using (command.Connection)
{
command.Connection.Open();
AppendTypes(command, requestParams);
returnScalar = command.ExecuteScalar();
command.Connection.Close();
}
return returnScalar;
}
public TResult ExecuteScalar<TResult>(RequestParams requestParams)
{
object returnScalar;
NpgsqlCommand command = CreateCommand(requestParams);
using (command.Connection)
{
command.Connection.Open();
AppendTypes(command, requestParams);
returnScalar = command.ExecuteScalar();
command.Connection.Close();
}
return (TResult)returnScalar;
}
private IEnumerable<TEntity> ExecuteSelectMany<TEntity>(SelectType extraSelectType, RequestParamsSelect<TEntity> requestParams)
{
if (requestParams.Converters == null || requestParams.Converters.Count() < 1) throw new ArgumentNullException("converters");
NpgsqlCommand command = CreateCommand(requestParams);
command.CommandTimeout = command.Connection.ConnectionTimeout;
IList<TEntity> selectResult = extraSelectType.CreateList<TEntity>();
using (command.Connection)
{
command.Connection.Open();
AppendTypes(command, requestParams);
using (NpgsqlTransaction tran = command.Connection.BeginTransaction())
{
using (IDataReader readerFirst = command.ExecuteReader())
{
var dataTypeName = readerFirst.GetDataTypeName(0); // Для первого столбца
if (dataTypeName == "refcursor")
{
ProcessForCursors(readerFirst, command, requestParams.Converters, selectResult, extraSelectType);
}
else
{
ProcessForSimple(readerFirst, requestParams.Converters, selectResult, extraSelectType);
}
}
tran.Commit();
}
}
return selectResult;
}
private void ProcessForSimple<TEntity>(IDataReader reader, IEnumerable<Action<IDataRecordSpecified, IList<TEntity>>> converters, IList<TEntity> selectResult, SelectType extraSelectType = SelectType.Default)
{
IDataRecordSpecified recordSpecified = new DataRecordSpecified(reader, Translator);
foreach (var converter in converters)
{
while (reader.Read())
{
converter(recordSpecified, selectResult);
}
reader.NextResult();
}
reader.ProcessExtraSelect(Translator, selectResult, extraSelectType);
}
private void CheckCursorCount(int cursorCount, int converterCount, SelectType extraSelectType)
{
switch (extraSelectType)
{
case SelectType.Page:
case SelectType.Offer:
Check.IsTrue(cursorCount >= converterCount + 1, "Количество конвертеров больше количества возвращаемых курсоров");
break;
case SelectType.Default:
default:
Check.IsTrue(cursorCount >= converterCount, "Количество конвертеров больше количества возвращаемых курсоров");
break;
}
}
private void ProcessForCursors<TEntity>(IDataReader readerFirst, NpgsqlCommand command, IEnumerable<Action<IDataRecordSpecified, IList<TEntity>>> converters, IList<TEntity> selectResult, SelectType extraSelectType = SelectType.Default)
{
var cursors = new List<string>();
while (readerFirst.Read())
{
cursors.Add(readerFirst[0].ToString()!);
}
readerFirst.Close();
CheckCursorCount(cursors.Count, converters.Count(), extraSelectType);
for (int i = 0; i < converters.Count(); i++)
{
command.CommandText = $"fetch all in \"{cursors[i]}\"";
command.CommandType = CommandType.Text;
using (NpgsqlDataReader reader = command.ExecuteReader())
{
IDataRecordSpecified recordSpecified = new DataRecordSpecified(reader, Translator);
while (reader.Read())
{
converters.ElementAt(i)(recordSpecified, selectResult);
}
}
}
if (extraSelectType != SelectType.Default)
{
// считывание данных постраничного вывода
command.CommandText = $"fetch all in \"{cursors.Last()}\"";
command.CommandType = CommandType.Text;
using (NpgsqlDataReader reader = command.ExecuteReader())
{
reader.ProcessExtraSelect(Translator, selectResult, extraSelectType);
}
}
}
public IEnumerable<TEntity> ExecuteSelectMany<TEntity>(RequestParamsSelect<TEntity> requestParams) => ExecuteSelectMany(SelectType.Default, requestParams);
public IEnumerableWithPage<TEntity> ExecuteSelectManyWithPage<TEntity>(RequestParamsSelect<TEntity> requestParams) => (ListWithPage<TEntity>)ExecuteSelectMany(SelectType.Page, requestParams);
public IEnumerableWithOffer<TEntity> ExecuteSelectManyWithOffer<TEntity>(RequestParamsSelect<TEntity> requestParams) => (ListWithOffer<TEntity>)ExecuteSelectMany(SelectType.Offer, requestParams);
}
public static class SqlHelperExtensions
{
public static TValue Get<TValue>(this IDataRecordSpecified record, string fieldName) => record.Get<TValue>(fieldName);
public static TValue GetN<TValue>(this IDataRecordSpecified record, string fieldName) => record.GetN<TValue>(fieldName);
}