using System.Diagnostics;
using System.Reflection;
using System.Text;
namespace CrupestApi.Commons.Crud;
public class ColumnHooks
{
// value:
// null => not specified
// DbNullValue => specified as NULL
// other => specified as value
public delegate void ColumnHookAction(ColumnInfo column, ref object? value);
public ColumnHooks(ColumnHookAction afterSelect, ColumnHookAction beforeInsert, ColumnHookAction beforeUpdate)
{
AfterSelect = afterSelect;
BeforeInsert = beforeInsert;
BeforeUpdate = beforeUpdate;
}
/// Called after SELECT. Please use multicast if you want to customize it because there are many default behavior in it.
///
/// Called after column type transformation.
/// value(in):
/// null => not found in SELECT result
/// DbNullValue => database NULL
/// others => database value
/// value(out):
/// null/DbNullValue => return null
/// others => return as is
///
public ColumnHookAction AfterSelect;
/// Called before INSERT. Please use multicast if you want to customize it because there are many default behavior in it.
///
/// Called before column type transformation.
/// value(in):
/// null => not specified by insert clause
/// DbNullValue => specified as database NULL
/// other => specified as other value
/// value(out):
/// null/DbNullValue => save database NULL
/// other => save the value as is
///
public ColumnHookAction BeforeInsert;
/// Called before UPDATE. Please use multicast if you want to customize it because there are many default behavior in it.
/// Called before column type transformation.
/// value(in):
/// null => not specified by update clause
/// DbNullValue => specified as database NULL
/// other => specified as other value
/// value(out):
/// null => not update
/// DbNullValue => update to database NULL
/// other => update to the value
///
public ColumnHookAction BeforeUpdate;
}
public class ColumnInfo
{
private readonly AggregateColumnMetadata _metadata = new AggregateColumnMetadata();
///
/// Initialize a column without corresponding property.
///
public ColumnInfo(TableInfo table, IColumnMetadata metadata, Type clrType, IColumnTypeProvider typeProvider)
{
Table = table;
_metadata.Add(metadata);
ColumnType = typeProvider.Get(clrType);
Hooks = new ColumnHooks(
new ColumnHooks.ColumnHookAction(OnAfterSelect),
new ColumnHooks.ColumnHookAction(OnBeforeInsert),
new ColumnHooks.ColumnHookAction(OnBeforeUpdate)
);
}
///
/// Initialize a column with corresponding property.
///
public ColumnInfo(TableInfo table, PropertyInfo propertyInfo, IColumnTypeProvider typeProvider)
{
Table = table;
PropertyInfo = propertyInfo;
ColumnType = typeProvider.Get(propertyInfo.PropertyType);
var columnAttribute = propertyInfo.GetCustomAttribute();
if (columnAttribute is not null)
{
_metadata.Add(columnAttribute);
}
Hooks = new ColumnHooks(
new ColumnHooks.ColumnHookAction(OnAfterSelect),
new ColumnHooks.ColumnHookAction(OnBeforeInsert),
new ColumnHooks.ColumnHookAction(OnBeforeUpdate)
);
}
public TableInfo Table { get; }
// If null, there is no corresponding property.
public PropertyInfo? PropertyInfo { get; } = null;
public IColumnMetadata Metadata => _metadata;
public IColumnTypeInfo ColumnType { get; }
public ColumnHooks Hooks { get; }
public bool IsPrimaryKey => Metadata.GetValueOrDefault(ColumnMetadataKeys.IsPrimaryKey) is true;
public bool IsAutoIncrement => Metadata.GetValueOrDefault(ColumnMetadataKeys.IsAutoIncrement) is true;
public bool IsNotNull => IsPrimaryKey || Metadata.GetValueOrDefault(ColumnMetadataKeys.NotNull) is true;
public bool IsGenerated => Metadata.GetValueOrDefault(ColumnMetadataKeys.Generated) is true;
public bool IsNoUpdate => Metadata.GetValueOrDefault(ColumnMetadataKeys.NoUpdate) is true;
public bool CanBeGenerated => (bool?)Metadata.GetValueOrDefault(ColumnMetadataKeys.CanBeGenerated) ?? (DefaultValueGeneratorMethod is not null);
///
/// This only returns metadata value. It doesn't not fall back to primary column. If you want to get the real key column, go to table info.
///
///
///
public bool IsSpecifiedAsKey => Metadata.GetValueOrDefault(ColumnMetadataKeys.ActAsKey) is true;
public ColumnIndexType Index => Metadata.GetValueOrDefault(ColumnMetadataKeys.Index) ?? ColumnIndexType.None;
///
/// The real column name. Maybe set in metadata or just the property name.
///
///
public string ColumnName
{
get
{
object? value = Metadata.GetValueOrDefault(ColumnMetadataKeys.ColumnName);
Debug.Assert(value is null || value is string);
return ((string?)value ?? PropertyInfo?.Name) ?? throw new Exception("Failed to get column name.");
}
}
public MethodInfo? DefaultValueGeneratorMethod
{
get
{
object? value = Metadata.GetValueOrDefault(ColumnMetadataKeys.DefaultValueGenerator);
Debug.Assert(value is null || value is string);
MethodInfo? result;
if (value is null)
{
string methodName = ColumnName + "DefaultValueGenerator";
result = Table.EntityType.GetMethod(methodName, BindingFlags.Static);
}
else
{
string methodName = (string)value;
result = Table.EntityType.GetMethod(methodName, BindingFlags.Static) ?? throw new Exception("The default value generator does not exist.");
}
return result;
}
}
public MethodInfo ValidatorMethod
{
get
{
object? value = Metadata.GetValueOrDefault(ColumnMetadataKeys.Validator);
Debug.Assert(value is null || value is string);
if (value is null)
{
return GetType().GetMethod(nameof(DefaultValidator))!;
}
else
{
string methodName = (string)value;
return Table.EntityType.GetMethod(methodName, BindingFlags.Static) ?? throw new Exception("The validator does not exist.");
}
}
}
public void InvokeValidator(object value)
{
ValidatorMethod.Invoke(null, new object?[] { this, value });
}
public static void DefaultValidator(ColumnInfo column, object value)
{
if (column.IsNotNull && value is DbNullValue)
{
throw new Exception("The column can't be null.");
}
}
public object? InvokeDefaultValueGenerator()
{
return DefaultValueGeneratorMethod?.Invoke(null, new object?[] { this });
}
public static object? DefaultDefaultValueGenerator(ColumnInfo column)
{
return DbNullValue.Instance;
}
private void TryCoerceStringFromNullToEmpty(ref object? value)
{
if (ColumnType.ClrType == typeof(string) && (Metadata.GetValueOrDefault(ColumnMetadataKeys.DefaultEmptyForString) is true) && value is DbNullValue)
{
value = "";
}
}
protected void OnAfterSelect(ColumnInfo column, ref object? value)
{
TryCoerceStringFromNullToEmpty(ref value);
}
protected void OnBeforeInsert(ColumnInfo column, ref object? value)
{
TryCoerceStringFromNullToEmpty(ref value);
}
protected void OnBeforeUpdate(ColumnInfo column, ref object? value)
{
if (IsNoUpdate && value is not null)
throw new Exception("The column can't be updated.");
TryCoerceStringFromNullToEmpty(ref value);
}
public string GenerateCreateTableColumnString(string? dbProviderId = null)
{
StringBuilder result = new StringBuilder();
result.Append(ColumnName);
result.Append(' ');
result.Append(ColumnType.GetSqlTypeString(dbProviderId));
if (IsPrimaryKey)
{
result.Append(' ');
result.Append("PRIMARY KEY");
}
else if (IsNotNull)
{
result.Append(' ');
result.Append(" NOT NULL");
}
if (IsAutoIncrement)
{
result.Append(' ');
result.Append("AUTOINCREMENT");
}
return result.ToString();
}
}