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

577 lines
19 KiB
C#

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<KeyValuePair<string, object>>? Parameters { get; set; }
public IEnumerable<CustomType>? CustomTypes { get; set; }
}
public class RequestParamsSelect<TEntity> : RequestParams
{
public IEnumerable<Action<IDataRecordSpecified, IList<TEntity>>> Converters { get; set; } = Enumerable.Empty<Action<IDataRecordSpecified, IList<TEntity>>>();
}
public interface ISqlHelper
{
void ExecuteNonQuery(RequestParams requestParams);
object ExecuteScalar(RequestParams requestParams);
TResult ExecuteScalar<TResult>(RequestParams requestParams);
IEnumerable<TEntity> ExecuteSelectMany<TEntity>(RequestParamsSelect<TEntity> requestParams);
IEnumerableWithPage<TEntity> ExecuteSelectManyWithPage<TEntity>(RequestParamsSelect<TEntity> requestParams);
IEnumerableWithOffer<TEntity> ExecuteSelectManyWithOffer<TEntity>(RequestParamsSelect<TEntity> requestParams);
}
public interface ISqlHelperFluent
{
ISqlHelperFluent AsStoredProcedure();
ISqlHelperFluent AsSqlText();
ISqlHelperFluent WithStrictSyntax();
ISqlHelperFluent AddCustomType(string name, Type type);
ISqlHelperFluent AddCustomType<TType>(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<KeyValuePair<string, object>> parameters);
ISqlHelperFluent AddFilters(IEnumerable<KeyValuePair<string, object>> parameters);
void ExecuteNonQuery();
TResult ExecuteScalar<TResult>();
}
public interface ISqlHelperFluent<TEntity>
{
ISqlHelperFluent<TEntity> AsStoredProcedure();
ISqlHelperFluent<TEntity> AsSqlText();
ISqlHelperFluent<TEntity> WithStrictSyntax();
ISqlHelperFluent<TEntity> AddCustomType(string name, Type type);
ISqlHelperFluent<TEntity> AddCustomType<TType>(string name);
ISqlHelperFluent<TEntity> AddFilter(string name, object value);
ISqlHelperFluent<TEntity> AddFilter(string name, string value);
ISqlHelperFluent<TEntity> AddParameter(string name, object value);
ISqlHelperFluent<TEntity> AddParameter(string name, string value);
ISqlHelperFluent<TEntity> AddParameterNullable(string name, object? value);
ISqlHelperFluent<TEntity> AddParameterIfNotNull(string name, object? value);
ISqlHelperFluent<TEntity> AddOutputParameter(string name, object value);
ISqlHelperFluent<TEntity> AddParameters(IEnumerable<KeyValuePair<string, object>> parameters);
ISqlHelperFluent<TEntity> AddFilters(IEnumerable<KeyValuePair<string, object>> parameters);
ISqlHelperFluent<TEntity> AddConverter(Action<IDataRecordSpecified, IList<TEntity>> converter);
IEnumerable<TEntity> ExecuteSelectMany();
IEnumerableWithPage<TEntity> ExecuteSelectManyWithPage();
IEnumerableWithOffer<TEntity> 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<CustomType> _customTypes;
protected List<KeyValuePair<string, object>> _parameters;
public SqlHelperFluent(IConnectionString connectionString, string sqlText)
{
_connectionString = connectionString;
_sqlText = sqlText;
_isStoredProcedure = true;
_isOutputParameter = false;
_withStrictSyntax = false;
_customTypes = new List<CustomType>();
_parameters = new List<KeyValuePair<string, object>>();
}
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<TType>(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<string, object>(name, value));
}
return this;
}
public ISqlHelperFluent AddFilter(string name, string value)
{
if (!value.IsNullOrEmpty())
{
_parameters.Add(new KeyValuePair<string, object>(name, value));
}
return this;
}
public ISqlHelperFluent AddParameter(string name, object value)
{
_parameters.Add(new KeyValuePair<string, object>(name, value));
return this;
}
public ISqlHelperFluent AddParameter(string name, string value)
{
_parameters.Add(new KeyValuePair<string, object>(name, value ?? string.Empty));
return this;
}
public ISqlHelperFluent AddParameterNullable(string name, object? value)
{
_parameters.Add(new KeyValuePair<string, object>(name, value ?? DBNull.Value));
return this;
}
public ISqlHelperFluent AddParameterIfNotNull(string name, object? value)
{
if (value != null)
{
_parameters.Add(new KeyValuePair<string, object>(name, value));
}
return this;
}
public ISqlHelperFluent AddOutputParameter(string name, object value)
{
_parameters.Add(new KeyValuePair<string, object>(name, new OutputValue { Value = value ?? DBNull.Value }));
return this;
}
public ISqlHelperFluent AddParameters(IEnumerable<KeyValuePair<string, object>> parameters)
{
_parameters.AddRange(parameters);
return this;
}
public ISqlHelperFluent AddFilters(IEnumerable<KeyValuePair<string, object>> 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<TResult>()
{
return GetSqlHelper().ExecuteScalar<TResult>(BuildParams());
}
#endregion
}
public class SqlHelperFluent<TEntity> : SqlHelperFluent, ISqlHelperFluent<TEntity>
{
protected List<Action<IDataRecordSpecified, IList<TEntity>>> _converters;
public SqlHelperFluent(IConnectionString connectionString, string sqlText) : base(connectionString, sqlText)
{
_converters = new List<Action<IDataRecordSpecified, IList<TEntity>>>();
}
#region base fluent methods
public new ISqlHelperFluent<TEntity> AddCustomType(string name, Type type)
{
base.AddCustomType(name, type);
return this;
}
public new ISqlHelperFluent<TEntity> AddCustomType<TType>(string name)
{
base.AddCustomType<TType>(name);
return this;
}
public new ISqlHelperFluent<TEntity> AddFilter(string name, object value)
{
base.AddFilter(name, value);
return this;
}
public new ISqlHelperFluent<TEntity> AddFilter(string name, string value)
{
base.AddFilter(name, value);
return this;
}
public new ISqlHelperFluent<TEntity> AddParameter(string name, object value)
{
base.AddParameter(name, value);
return this;
}
public new ISqlHelperFluent<TEntity> AddParameter(string name, string value)
{
base.AddParameter(name, value);
return this;
}
public new ISqlHelperFluent<TEntity> AddParameterNullable(string name, object? value)
{
base.AddParameterNullable(name, value);
return this;
}
public new ISqlHelperFluent<TEntity> AddParameterIfNotNull(string name, object? value)
{
base.AddParameterIfNotNull(name, value);
return this;
}
public new ISqlHelperFluent<TEntity> AddOutputParameter(string name, object value)
{
base.AddParameter(name, value);
return this;
}
public new ISqlHelperFluent<TEntity> AddParameters(IEnumerable<KeyValuePair<string, object>> parameters)
{
base.AddParameters(parameters);
return this;
}
public new ISqlHelperFluent<TEntity> AddFilters(IEnumerable<KeyValuePair<string, object>> parameters)
{
base.AddFilters(parameters);
return this;
}
public new ISqlHelperFluent<TEntity> AsStoredProcedure()
{
base.AsStoredProcedure();
return this;
}
public new ISqlHelperFluent<TEntity> AsSqlText()
{
base.AsSqlText();
return this;
}
public new ISqlHelperFluent<TEntity> WithStrictSyntax()
{
base.WithStrictSyntax();
return this;
}
#endregion
private RequestParamsSelect<TEntity> BuildParams() => new RequestParamsSelect<TEntity>
{
ConnectionString = _connectionString,
CommandText = _sqlText,
IsStoredProcedure = _isStoredProcedure,
WithStrictSyntax = _withStrictSyntax,
Parameters = _parameters,
CustomTypes = _customTypes,
Converters = _converters,
};
public ISqlHelperFluent<TEntity> AddConverter(Action<IDataRecordSpecified, IList<TEntity>> converter)
{
_converters.Add(converter);
return this;
}
public IEnumerable<TEntity> ExecuteSelectMany()
{
return GetSqlHelper().ExecuteSelectMany(BuildParams());
}
public IEnumerableWithPage<TEntity> ExecuteSelectManyWithPage()
{
return GetSqlHelper().ExecuteSelectManyWithPage(BuildParams());
}
public IEnumerableWithOffer<TEntity> 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<TEntity> PrepareExecute<TEntity>(this IConnectionString connectionString, string sqlText)
{
return new SqlHelperFluent<TEntity>(connectionString, sqlText);
}
}
public interface IDataRecordSpecified
{
IDataRecord Src { get; }
Npgsql.INpgsqlNameTranslator NameTranslator { get; }
TValue Convert<TValue>(object value);
}
internal class DataRecordSpecified : IDataRecordSpecified
{
public IDataRecord Src { get; }
public Npgsql.INpgsqlNameTranslator NameTranslator { get; }
public virtual TValue Convert<TValue>(object value)
{
return (TValue)value;
}
public DataRecordSpecified(IDataReader dataReader, Npgsql.INpgsqlNameTranslator nameTranslator)
{
Src = dataReader;
NameTranslator = nameTranslator;
}
}
public static class SqlHelperExtensions
{
/// <summary>
/// Проверка на наличие столбца в запросе
/// </summary>
/// <param name="reader"></param>
/// <param name="name"></param>
/// <returns></returns>
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<TValue>(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<TValue>(value);
}
catch (InvalidCastException ex)
{
throw new InvalidCastException(string.Format("{0} {1} нельзя привести к {2}", fieldName, value.GetType().FullName, typeof(TValue).FullName), ex);
}
}
public static TValue Get<TValue>(this IDataRecordSpecified record, string fieldName) => record.TryGet<TValue>(fieldName, isStrict: true)!;
public static TValue GetN<TValue>(this IDataRecordSpecified record, string fieldName) => record.TryGet<TValue>(fieldName, isStrict: false)!;
internal static IList<TEntity> CreateList<TEntity>(this SelectType extraSelectType)
{
switch (extraSelectType)
{
case SelectType.Page:
return new ListWithPage<TEntity>();
case SelectType.Offer:
return new ListWithOffer<TEntity>();
case SelectType.Default:
default:
return new List<TEntity>();
}
}
internal static void ProcessExtraSelect<TEntity>(this IDataReader reader, Npgsql.INpgsqlNameTranslator translator, IList<TEntity> selectResult, SelectType extraSelectType = SelectType.Default)
{
IDataRecordSpecified readerSpecified = new DataRecordSpecified(reader, translator);
switch (extraSelectType)
{
case SelectType.Page:
{
var result = (IEnumerableWithPage<TEntity>)selectResult;
// считывание данных постраничного вывода
while (reader.Read())
{
if (readerSpecified.HasColumn("FilteredRows")) result.FilteredRows = readerSpecified.Get<int>("filteredRows");
if (readerSpecified.HasColumn("TotalRows")) result.TotalRows = readerSpecified.Get<int>("totalRows");
if (readerSpecified.HasColumn("PageNo")) result.PageNo = readerSpecified.Get<int>("pageNo");
if (readerSpecified.HasColumn("PageSize")) result.PageSize = readerSpecified.Get<int>("pageSize");
if (readerSpecified.HasColumn("Sort")) result.Sort = readerSpecified.Get<string>("sort");
if (readerSpecified.HasColumn("Dir")) result.Dir = readerSpecified.Get<string>("dir");
}
}
break;
case SelectType.Offer:
{
var result = (IEnumerableWithOffer<TEntity>)selectResult;
// считывание данных постраничного вывода
while (reader.Read())
{
if (readerSpecified.HasColumn("FilteredRows")) result.FilteredRows = readerSpecified.Get<int>("FilteredRows");
if (readerSpecified.HasColumn("TotalRows")) result.TotalRows = readerSpecified.Get<int>("TotalRows");
if (readerSpecified.HasColumn("Start")) result.Start = readerSpecified.Get<int>("Start");
if (readerSpecified.HasColumn("Length")) result.Lenght = readerSpecified.Get<int>("Length");
if (readerSpecified.HasColumn("Sort")) result.Sort = readerSpecified.Get<string>("Sort");
if (readerSpecified.HasColumn("Dir")) result.Dir = readerSpecified.Get<string>("Dir");
}
}
break;
case SelectType.Default:
default:
break;
}
}
}
internal enum SelectType
{
Default,
Page,
Offer
}