You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Connected.Framework/Connected.Data/Update/CommandBuilder.cs

217 lines
6.0 KiB

using Connected.Entities;
using Connected.Entities.Annotations;
using Connected.Entities.Storage;
using Connected.Interop;
using System.Data;
using System.Reflection;
using System.Text;
namespace Connected.Data.Update;
internal abstract class CommandBuilder
{
private readonly List<IStorageParameter> _parameters;
private readonly List<PropertyInfo> _whereProperties;
private List<PropertyInfo> _properties;
protected CommandBuilder()
{
_parameters = new List<IStorageParameter>();
_whereProperties = new List<PropertyInfo>();
Text = new StringBuilder();
}
public StorageOperation? Build(IEntity entity)
{
Entity = entity;
if (TryGetExisting(out StorageOperation? existing))
{
/*
* We need to rebuild an instance since StorageOperation
* is immutable
*/
var result = new StorageOperation
{
CommandText = existing.CommandText,
CommandTimeout = existing.CommandTimeout,
CommandType = existing.CommandType,
Concurrency = existing.Concurrency
};
if (result.Parameters is null)
return result;
foreach (var parameter in result.Parameters)
{
if (parameter.Direction == ParameterDirection.Input)
{
if (ResolveProperty(parameter.Name) is PropertyInfo property)
{
result.AddParameter(new StorageParameter
{
Value = GetValue(property),
Name = parameter.Name,
Type = parameter.Type,
Direction = parameter.Direction
});
}
}
}
return result;
}
Schema = Entity.GetSchemaAttribute();
return OnBuild();
}
protected List<PropertyInfo> Properties => _properties ??= GetProperties();
protected List<PropertyInfo> WhereProperties => _whereProperties;
protected string CommandText => Text.ToString();
protected IEntity Entity { get; private set; }
protected SchemaAttribute Schema { get; private set; }
private StringBuilder Text { get; set; }
protected abstract StorageOperation OnBuild();
protected abstract bool TryGetExisting(out StorageOperation? result);
protected List<IStorageParameter> Parameters => _parameters;
protected void Write(string text)
{
Text.Append(text);
}
protected void Write(char text)
{
Text.Append(text);
}
protected void WriteLine(string text)
{
Text.AppendLine(text);
}
protected void Trim()
{
for (var i = Text.Length - 1; i >= 0; i--)
{
if (!Text[i].Equals(',') && !Text[i].Equals('\n') && !Text[i].Equals('\r') && !Text[i].Equals(' '))
break;
if (i < Text.Length)
Text.Length = i;
}
}
protected virtual List<PropertyInfo> GetProperties()
{
return Interop.Properties.GetImplementedProperties(Entity);
}
protected static bool IsVersion(PropertyInfo property)
{
return property.GetCustomAttribute<ETagAttribute>() is not null;
}
protected static string ColumnName(PropertyInfo property)
{
var dataMember = property.FindAttribute<MemberAttribute>();
return dataMember is null || string.IsNullOrEmpty(dataMember.Member) ? property.Name.ToCamelCase() : dataMember.Member;
}
protected static DbType ResolveDbType(PropertyInfo property)
{
if (IsVersion(property))
return DbType.Binary;
return property.PropertyType.ToDbType();
}
protected object? GetValue(PropertyInfo property)
{
if (IsNull(property))
return "NULL";
if (IsVersion(property))
return (byte[])EntityVersion.Parse(property.GetValue(Entity));
return GetValue(property.GetValue(Entity), property.PropertyType.ToDbType());
}
private static object? GetValue(object value, DbType dbType)
{
switch (dbType)
{
case DbType.Binary:
if (value is byte[] bytes)
return Convert.ToBase64String(bytes);
else
return Convert.ToBase64String(Encoding.UTF8.GetBytes(value.ToString()));
default:
return value;
}
}
private bool IsNull(PropertyInfo property)
{
var result = property.GetValue(Entity);
if (result is null)
return true;
if (property.GetCustomAttribute<NullableAttribute>() is null)
return false;
var def = Types.GetDefault(property.PropertyType);
return TypeComparer.Compare(result, def);
}
protected StorageParameter CreateParameter(PropertyInfo property)
{
return CreateParameter(property, ParameterDirection.Input);
}
protected StorageParameter CreateParameter(PropertyInfo property, ParameterDirection direction)
{
var columnName = ColumnName(property);
var parameterName = $"@{columnName}";
var parameter = new StorageParameter
{
Direction = direction,
Name = parameterName,
Type = ResolveDbType(property),
Value = GetValue(property)
};
Parameters.Add(parameter);
return parameter;
}
private PropertyInfo ResolveProperty(string parameterName)
{
var propertyName = parameterName[1..];
var flags = BindingFlags.Public | BindingFlags.Instance | BindingFlags.NonPublic;
if (Entity.GetType().GetProperty(propertyName.ToPascalCase(), flags) is PropertyInfo property)
return property;
if (Entity.GetType().GetProperty(propertyName, flags) is PropertyInfo raw)
return raw;
return null;
}
}