|
|
@ -0,0 +1,206 @@
|
|
|
|
|
|
|
|
using System.Data;
|
|
|
|
|
|
|
|
using System.Reflection;
|
|
|
|
|
|
|
|
using System.Text;
|
|
|
|
|
|
|
|
using Connected.Entities;
|
|
|
|
|
|
|
|
using Connected.Entities.Annotations;
|
|
|
|
|
|
|
|
using Connected.Entities.Storage;
|
|
|
|
|
|
|
|
using Connected.Interop;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
namespace Connected.ServiceModel.Client.Data;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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 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)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
return property.PropertyType.ToDbType();
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
protected object? GetValue(PropertyInfo property)
|
|
|
|
|
|
|
|
{
|
|
|
|
|
|
|
|
if (IsNull(property))
|
|
|
|
|
|
|
|
return "NULL";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|