264 lines
9.7 KiB
C#
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);
|
|
} |