577 lines
19 KiB
C#
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
|
|
} |