VEU.WathBody/3-app/1-lib/Kit.Helpers/SqlHelperAbstract/SqlHelperPostgres.cs

280 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

using Npgsql;
using Npgsql.NameTranslation;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
namespace Kit.Helpers.SqlHelperAbstract.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 =>
{
// Имя параметра транслируется в snake_case как есть (без добавления "_"):
// должно совпадать с именем параметра хранимой функции (p_doc_code и т.п.),
// т.к. вызов строится именованной нотацией: select * from fn(p_doc_code => :p_doc_code).
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(".");
}
// Композиты, уже зарегистрированные в глобальном маппере (Npgsql 10:
// повторный MapComposite той же пары тип/имя бросает исключение).
private static readonly System.Collections.Concurrent.ConcurrentDictionary<(Type Type, string Name), bool> MappedComposites
= new System.Collections.Concurrent.ConcurrentDictionary<(Type, string), bool>();
private NpgsqlCommand AppendTypes(NpgsqlCommand command, RequestParams requestParams)
{
requestParams.CustomTypes?.ForEach(x =>
{
string name = TranslateName(x.Name, requestParams.WithStrictSyntax);
if (MappedComposites.TryAdd((x.Type, name), true))
{
NpgsqlConnection.GlobalTypeMapper.MapComposite(x.Type, name, 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();
}
recordSpecified.ProcessExtraSelect(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())
{
IDataRecordSpecified recordSpecified = new DataRecordSpecified(reader, Translator);
recordSpecified.ProcessExtraSelect(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);
}
}