using System.Data; using System.Text.Json.Serialization; namespace Kit.Helpers; public class OutputValue { public object Value; } public class CustomType { public string Name { get; set; } public Type Type { get; set; } public CustomType(string name, Type type) { Name = name; Type = type; } } public class RequestParams { public IConnectionString ConnectionString { get; set; } = new ConnectionString(); public string CommandText { get; set; } = string.Empty; public bool IsStoredProcedure { get; set; } = false; public bool WithStrictSyntax { get; set; } = false; public IEnumerable>? Parameters { get; set; } public IEnumerable? CustomTypes { get; set; } } public class RequestParamsSelect : RequestParams { public IEnumerable>> Converters { get; set; } = Enumerable.Empty>>(); } public interface ISqlHelper { void ExecuteNonQuery(RequestParams requestParams); object ExecuteScalar(RequestParams requestParams); TResult ExecuteScalar(RequestParams requestParams); IEnumerable ExecuteSelectMany(RequestParamsSelect requestParams); IEnumerableWithPage ExecuteSelectManyWithPage(RequestParamsSelect requestParams); IEnumerableWithOffer ExecuteSelectManyWithOffer(RequestParamsSelect requestParams); } public interface ISqlHelperFluent { ISqlHelperFluent AsStoredProcedure(); ISqlHelperFluent AsSqlText(); ISqlHelperFluent WithStrictSyntax(); ISqlHelperFluent AddCustomType(string name, Type type); ISqlHelperFluent AddCustomType(string name); ISqlHelperFluent AddFilter(string name, object value); ISqlHelperFluent AddFilter(string name, string value); ISqlHelperFluent AddParameter(string name, object value); ISqlHelperFluent AddParameter(string name, string value); ISqlHelperFluent AddParameterNullable(string name, object? value); ISqlHelperFluent AddParameterIfNotNull(string name, object? value); ISqlHelperFluent AddOutputParameter(string name, object value); ISqlHelperFluent AddParameters(IEnumerable> parameters); ISqlHelperFluent AddFilters(IEnumerable> parameters); void ExecuteNonQuery(); TResult ExecuteScalar(); } public interface ISqlHelperFluent { ISqlHelperFluent AsStoredProcedure(); ISqlHelperFluent AsSqlText(); ISqlHelperFluent WithStrictSyntax(); ISqlHelperFluent AddCustomType(string name, Type type); ISqlHelperFluent AddCustomType(string name); ISqlHelperFluent AddFilter(string name, object value); ISqlHelperFluent AddFilter(string name, string value); ISqlHelperFluent AddParameter(string name, object value); ISqlHelperFluent AddParameter(string name, string value); ISqlHelperFluent AddParameterNullable(string name, object? value); ISqlHelperFluent AddParameterIfNotNull(string name, object? value); ISqlHelperFluent AddOutputParameter(string name, object value); ISqlHelperFluent AddParameters(IEnumerable> parameters); ISqlHelperFluent AddFilters(IEnumerable> parameters); ISqlHelperFluent AddConverter(Action> converter); IEnumerable ExecuteSelectMany(); IEnumerableWithPage ExecuteSelectManyWithPage(); IEnumerableWithOffer ExecuteSelectManyWithOffer(); } public enum ConnectionStringType { Undefined = 0, Postgres = 1, SQLite = 2, } public interface IConnectionString { ConnectionStringType Type { get; } string Value { get; } [JsonIgnore] object? DatabaseConnection { get; } } public class ConnectionString : IConnectionString { public ConnectionStringType Type { get; set; } = ConnectionStringType.Undefined; private string _value = string.Empty; public string Value { get => _value; set => _value = value.ServerMapPath(); } [JsonIgnore] public object? DatabaseConnection { get; set; } public void CreateAndOpenConnection() { switch (Type) { case ConnectionStringType.Postgres: DatabaseConnection = new Npgsql.NpgsqlConnection(Value.CreateSQLiteConnectionString()); (DatabaseConnection as Npgsql.NpgsqlConnection)!.Open(); break; case ConnectionStringType.SQLite: DatabaseConnection = new Microsoft.Data.Sqlite.SqliteConnection(Value.CreateSQLiteConnectionString()); (DatabaseConnection as Microsoft.Data.Sqlite.SqliteConnection)!.Open(); break; default: throw new NotImplementedException(); } } ~ConnectionString() { if (DatabaseConnection is IDisposable disposable) disposable.Dispose(); DatabaseConnection = null; } } public class SqlHelperFluent : ISqlHelperFluent { protected readonly IConnectionString _connectionString; protected readonly string _sqlText; protected bool _isStoredProcedure; protected bool _isOutputParameter; protected bool _withStrictSyntax; protected List _customTypes; protected List> _parameters; public SqlHelperFluent(IConnectionString connectionString, string sqlText) { _connectionString = connectionString; _sqlText = sqlText; _isStoredProcedure = true; _isOutputParameter = false; _withStrictSyntax = false; _customTypes = new List(); _parameters = new List>(); } protected ISqlHelper GetSqlHelper() { switch (_connectionString.Type) { case ConnectionStringType.Postgres: return Postgres.SqlHelper.Instance; case ConnectionStringType.SQLite: return SQLite.SqlHelper.Instance; default: throw new NotImplementedException(); } } #region fluent methods public ISqlHelperFluent AsStoredProcedure() { _isStoredProcedure = true; return this; } public ISqlHelperFluent AsSqlText() { _isStoredProcedure = false; return this; } public ISqlHelperFluent WithStrictSyntax() { _withStrictSyntax = true; return this; } public ISqlHelperFluent AddCustomType(string name, Type type) { if (type != null) { _customTypes.Add(new CustomType(name, type)); } return this; } public ISqlHelperFluent AddCustomType(string name) { _customTypes.Add(new CustomType(name, typeof(TType))); return this; } public ISqlHelperFluent AddFilter(string name, object value) { if (value != null) { _parameters.Add(new KeyValuePair(name, value)); } return this; } public ISqlHelperFluent AddFilter(string name, string value) { if (!value.IsNullOrEmpty()) { _parameters.Add(new KeyValuePair(name, value)); } return this; } public ISqlHelperFluent AddParameter(string name, object value) { _parameters.Add(new KeyValuePair(name, value)); return this; } public ISqlHelperFluent AddParameter(string name, string value) { _parameters.Add(new KeyValuePair(name, value ?? string.Empty)); return this; } public ISqlHelperFluent AddParameterNullable(string name, object? value) { _parameters.Add(new KeyValuePair(name, value ?? DBNull.Value)); return this; } public ISqlHelperFluent AddParameterIfNotNull(string name, object? value) { if (value != null) { _parameters.Add(new KeyValuePair(name, value)); } return this; } public ISqlHelperFluent AddOutputParameter(string name, object value) { _parameters.Add(new KeyValuePair(name, new OutputValue { Value = value ?? DBNull.Value })); return this; } public ISqlHelperFluent AddParameters(IEnumerable> parameters) { _parameters.AddRange(parameters); return this; } public ISqlHelperFluent AddFilters(IEnumerable> parameters) { _parameters.AddRange(parameters.Where(x => x.Value != null)); return this; } #endregion #region finish methods private RequestParams BuildParams() => new RequestParams { ConnectionString = _connectionString, CommandText = _sqlText, IsStoredProcedure = _isStoredProcedure, WithStrictSyntax = _withStrictSyntax, Parameters = _parameters, CustomTypes = _customTypes, }; public void ExecuteNonQuery() { GetSqlHelper().ExecuteNonQuery(BuildParams()); } public TResult ExecuteScalar() { return GetSqlHelper().ExecuteScalar(BuildParams()); } #endregion } public class SqlHelperFluent : SqlHelperFluent, ISqlHelperFluent { protected List>> _converters; public SqlHelperFluent(IConnectionString connectionString, string sqlText) : base(connectionString, sqlText) { _converters = new List>>(); } #region base fluent methods public new ISqlHelperFluent AddCustomType(string name, Type type) { base.AddCustomType(name, type); return this; } public new ISqlHelperFluent AddCustomType(string name) { base.AddCustomType(name); return this; } public new ISqlHelperFluent AddFilter(string name, object value) { base.AddFilter(name, value); return this; } public new ISqlHelperFluent AddFilter(string name, string value) { base.AddFilter(name, value); return this; } public new ISqlHelperFluent AddParameter(string name, object value) { base.AddParameter(name, value); return this; } public new ISqlHelperFluent AddParameter(string name, string value) { base.AddParameter(name, value); return this; } public new ISqlHelperFluent AddParameterNullable(string name, object? value) { base.AddParameterNullable(name, value); return this; } public new ISqlHelperFluent AddParameterIfNotNull(string name, object? value) { base.AddParameterIfNotNull(name, value); return this; } public new ISqlHelperFluent AddOutputParameter(string name, object value) { base.AddParameter(name, value); return this; } public new ISqlHelperFluent AddParameters(IEnumerable> parameters) { base.AddParameters(parameters); return this; } public new ISqlHelperFluent AddFilters(IEnumerable> parameters) { base.AddFilters(parameters); return this; } public new ISqlHelperFluent AsStoredProcedure() { base.AsStoredProcedure(); return this; } public new ISqlHelperFluent AsSqlText() { base.AsSqlText(); return this; } public new ISqlHelperFluent WithStrictSyntax() { base.WithStrictSyntax(); return this; } #endregion private RequestParamsSelect BuildParams() => new RequestParamsSelect { ConnectionString = _connectionString, CommandText = _sqlText, IsStoredProcedure = _isStoredProcedure, WithStrictSyntax = _withStrictSyntax, Parameters = _parameters, CustomTypes = _customTypes, Converters = _converters, }; public ISqlHelperFluent AddConverter(Action> converter) { _converters.Add(converter); return this; } public IEnumerable ExecuteSelectMany() { return GetSqlHelper().ExecuteSelectMany(BuildParams()); } public IEnumerableWithPage ExecuteSelectManyWithPage() { return GetSqlHelper().ExecuteSelectManyWithPage(BuildParams()); } public IEnumerableWithOffer ExecuteSelectManyWithOffer() { return GetSqlHelper().ExecuteSelectManyWithOffer(BuildParams()); } } public static class SqLiteHelperFluentExtentions { public static ISqlHelperFluent PrepareExecute(this IConnectionString connectionString, string sqlText) { return new SqlHelperFluent(connectionString, sqlText); } public static ISqlHelperFluent PrepareExecute(this IConnectionString connectionString, string sqlText) { return new SqlHelperFluent(connectionString, sqlText); } } public interface IDataRecordSpecified { IDataRecord Src { get; } Npgsql.INpgsqlNameTranslator NameTranslator { get; } TValue Convert(object value); } internal class DataRecordSpecified : IDataRecordSpecified { public IDataRecord Src { get; } public Npgsql.INpgsqlNameTranslator NameTranslator { get; } public virtual TValue Convert(object value) { return (TValue)value; } public DataRecordSpecified(IDataReader dataReader, Npgsql.INpgsqlNameTranslator nameTranslator) { Src = dataReader; NameTranslator = nameTranslator; } } public static class SqlHelperExtensions { /// /// Проверка на наличие столбца в запросе /// /// /// /// public static bool HasColumn(this IDataRecordSpecified reader, string name) { string nameTranslated = reader.NameTranslator.TranslateMemberName(name).ToLower(); for (int n = 0; n < reader.Src.FieldCount; n++) { string fieldNameTranslated = reader.NameTranslator.TranslateMemberName(reader.Src.GetName(n)).ToLower(); if (fieldNameTranslated == nameTranslated) return true; } return false; } private static string GetTranslatedName(this IDataRecordSpecified record, string fieldName) => record.NameTranslator.TranslateMemberName(fieldName); private static TValue? TryGet(this IDataRecordSpecified record, string fieldName, bool isStrict) { fieldName = GetTranslatedName(record, fieldName); object value = record.Src[fieldName]; if (value == DBNull.Value) { if (isStrict) throw new InvalidCastException(string.Format("{0} {1} нельзя привести к {2}", fieldName, typeof(DBNull).FullName, typeof(TValue).FullName)); return default; } try { return record.Convert(value); } catch (InvalidCastException ex) { throw new InvalidCastException(string.Format("{0} {1} нельзя привести к {2}", fieldName, value.GetType().FullName, typeof(TValue).FullName), ex); } } public static TValue Get(this IDataRecordSpecified record, string fieldName) => record.TryGet(fieldName, isStrict: true)!; public static TValue GetN(this IDataRecordSpecified record, string fieldName) => record.TryGet(fieldName, isStrict: false)!; internal static IList CreateList(this SelectType extraSelectType) { switch (extraSelectType) { case SelectType.Page: return new ListWithPage(); case SelectType.Offer: return new ListWithOffer(); case SelectType.Default: default: return new List(); } } internal static void ProcessExtraSelect(this IDataReader reader, Npgsql.INpgsqlNameTranslator translator, IList selectResult, SelectType extraSelectType = SelectType.Default) { IDataRecordSpecified readerSpecified = new DataRecordSpecified(reader, translator); switch (extraSelectType) { case SelectType.Page: { var result = (IEnumerableWithPage)selectResult; // считывание данных постраничного вывода while (reader.Read()) { if (readerSpecified.HasColumn("FilteredRows")) result.FilteredRows = readerSpecified.Get("filteredRows"); if (readerSpecified.HasColumn("TotalRows")) result.TotalRows = readerSpecified.Get("totalRows"); if (readerSpecified.HasColumn("PageNo")) result.PageNo = readerSpecified.Get("pageNo"); if (readerSpecified.HasColumn("PageSize")) result.PageSize = readerSpecified.Get("pageSize"); if (readerSpecified.HasColumn("Sort")) result.Sort = readerSpecified.Get("sort"); if (readerSpecified.HasColumn("Dir")) result.Dir = readerSpecified.Get("dir"); } } break; case SelectType.Offer: { var result = (IEnumerableWithOffer)selectResult; // считывание данных постраничного вывода while (reader.Read()) { if (readerSpecified.HasColumn("FilteredRows")) result.FilteredRows = readerSpecified.Get("FilteredRows"); if (readerSpecified.HasColumn("TotalRows")) result.TotalRows = readerSpecified.Get("TotalRows"); if (readerSpecified.HasColumn("Start")) result.Start = readerSpecified.Get("Start"); if (readerSpecified.HasColumn("Length")) result.Lenght = readerSpecified.Get("Length"); if (readerSpecified.HasColumn("Sort")) result.Sort = readerSpecified.Get("Sort"); if (readerSpecified.HasColumn("Dir")) result.Dir = readerSpecified.Get("Dir"); } } break; case SelectType.Default: default: break; } } } internal enum SelectType { Default, Page, Offer }