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 _parameters; private readonly List _whereProperties; private List _properties; protected CommandBuilder() { _parameters = new List(); _whereProperties = new List(); 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 Properties => _properties ??= GetProperties(); protected List 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 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 GetProperties() { return Interop.Properties.GetImplementedProperties(Entity); } protected static bool IsVersion(PropertyInfo property) { return property.GetCustomAttribute() is not null; } protected static string ColumnName(PropertyInfo property) { var dataMember = property.FindAttribute(); 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() 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; } }