Skip to content

Instantly share code, notes, and snippets.

@d1820
Last active June 4, 2020 03:23
Show Gist options
  • Save d1820/b405f5bf1c2c1798b263ecba49fae919 to your computer and use it in GitHub Desktop.
Save d1820/b405f5bf1c2c1798b263ecba49fae919 to your computer and use it in GitHub Desktop.
AWS Aurora DataAPI expression mappers - .Net Core 3.1
public static class ColumnTypes
{
public const int STRING = 12;
public const int BOOL = -7;
public const int TIMESTAMP = 93;
}
public class RdsCreateUpdateDto
{
[RdsFieldMap("updatedOn", RdsFieldType.StringValue)]
public DateTimeOffset UpdatedOn { get; set; }
[RdsFieldMap("updatedBy", RdsFieldType.StringValue)]
public string UpdatedBy { get; set; }
[RdsFieldMap("createdOn", RdsFieldType.StringValue)]
public DateTimeOffset CreatedOn { get; set; }
[RdsFieldMap("createdBy", RdsFieldType.StringValue)]
public string CreatedBy { get; set; }
}
public class RdsExampleDto : RdsCreateUpdateDto
{
[RdsFieldMap("id", RdsFieldType.LongValue)]
public int Id { get; set; }
[RdsFieldMap("exampleId", RdsFieldType.StringValue)]
public string ExampleId { get; set; }
[RdsFieldMap("exampleName", RdsFieldType.StringValue)]
public string ExampleName { get; set; }
[RdsFieldMap("isActive", RdsFieldType.BooleanValue)]
public bool IsActive { get; set; }
[RdsFieldMap("isDeleted", RdsFieldType.BooleanValue)]
public bool IsDeleted { get; set; }
}
public enum RdsFieldType
{
Unknown,
ArrayValue,
BlobValue,
BooleanValue,
DoubleValue,
IsNull,
LongValue,
StringValue
}
public interface IExampleDataService
{
Task<RdsExampleDto[]> GetRdsExampleDtosAsync(D);
}
public class ExampleDataService : IExampleDataService
{
private readonly ExecuteStatementRequestFactory _executeStatementRequestFactory;
private readonly IAmazonRDSDataService _amazonRDSDataService;
private readonly IQueryResultMapper _queryResultMapper;
public ExampleDataService(ExecuteStatementRequestFactory executeStatementRequestFactory, IAmazonRDSDataService amazonRDSDataService,
IQueryResultMapper queryResultMapper)
{
_executeStatementRequestFactory = executeStatementRequestFactory;
_amazonRDSDataService = amazonRDSDataService;
_queryResultMapper = queryResultMapper;
}
public async Task<RdsExampleDto[]> GetRdsEXampleDtosAsync()
{
var request = _executeStatementRequestFactory.Create();
var selectFields = _queryResultMapper.MapToSelectString("d", BuildExampleSelectFields().ToArray());
request.Sql = $"Select {selectFields} from EXAMPLES d WHERE d.isActive = :isActive AND d.isDeleted = 0";
request.Parameters.Add(new SqlParameter
{
Name = "isActive",
Value = new Field
{
BooleanValue = filterOptions.ActiveOnly
}
});
var re = await _amazonRDSDataService.ExecuteStatementAsync(request);
var results = new List<RdsExampleDto>();
foreach (var fields in re.Records)
{
var dis = _queryResultMapper.MapToExample(re.ColumnMetadata, fields);
results.Add(dis);
}
return results.ToArray();
}
private List<Expression<Func<RdsExampleFDto, object>>> BuildExampleSelectFields()
{
var fieldsToSelect = new List<Expression<Func<RdsExampleDto, object>>> {
p => p.ExampleId,
p => p.ExampleName,
p => p.IsActive,
p => p.IsDeleted,
p => p.CreatedBy,
p => p.CreatedOn,
p => p.UpdatedBy,
p => p.UpdatedOn };
return fieldsToSelect;
}
}
public class ExecuteStatementRequestFactory
{
private readonly string _databaseName;
private readonly string _resourceArn;
private readonly string _secretArn;
public ExecuteStatementRequestFactory(string databaseName, string resourceArn, string secretArn)
{
_databaseName = databaseName;
_resourceArn = resourceArn;
_secretArn = secretArn;
}
public ExecuteStatementRequest Create(bool includeResultMetadata = true)
{
return new ExecuteStatementRequest
{
Database = _databaseName,
ResourceArn = _resourceArn,
SecretArn = _secretArn,
IncludeResultMetadata = includeResultMetadata,
ResultSetOptions = new ResultSetOptions { DecimalReturnType = DecimalReturnType.DOUBLE_OR_LONG }
};
}
}
public interface IQueryResultMapper
{
string MapToSelectString<T>(string selectPrefix = null, params Expression<Func<T, object>>[] expressions);
RdsExampleDto MapToExample(List<ColumnMetadata> columns, List<Field> fields);
}
[AttributeUsage(AttributeTargets.Property, Inherited = false)]
public class RdsFieldMapAttribute : Attribute
{
public RdsFieldMapAttribute(string dbFieldName, RdsFieldType fieldType)
{
DbFieldName = dbFieldName;
FieldType = fieldType;
}
public string DbFieldName { get; }
public class RdsQueryResultMapper: IQueryResultMapper
{
public static Func<T> Instance<T>() where T : new()
{
return Expression.Lambda<Func<T>>(Expression.New(typeof(T))).Compile();
}
//RdsExampleDto is the entity that gets mapped from the resultMetadata from Aurora DataAPI
public RdsExampleDto MapToExample(List<ColumnMetadata> columns, List<Field> fields)
{
var dis = Instance<RdsExampleDto>()();
SetProp(dis, p => p.CreatedOn, columns, fields);
SetProp(dis, p => p.CreatedBy, columns, fields);
SetProp(dis, p => p.UpdatedBy, columns, fields);
SetProp(dis, p => p.UpdatedOn, columns, fields);
SetProp(dis, p => p.ExampleName, columns, fields);
SetProp(dis, p => p.ExampleId, columns, fields);
SetProp(dis, p => p.IsDeleted, columns, fields);
SetProp(dis, p => p.IsActive, columns, fields);
return dis;
}
public string MapToSelectString<T>(string selectPrefix = null, params Expression<Func<T, object>>[] expressions)
{
var selectParts = new List<string>();
foreach (var exp in expressions)
{
var attr = GetRdsFieldMapValues(exp);
if (!string.IsNullOrEmpty(attr.dbFieldame))
{
if (!string.IsNullOrEmpty(selectPrefix))
{
selectParts.Add($"{selectPrefix}.{attr.dbFieldame}");
}
else
{
selectParts.Add(attr.dbFieldame);
}
}
}
return string.Join(",", selectParts);
}
private bool SetProp<T, TP>(T entity, Expression<Func<T, TP>> propertyExpression, List<ColumnMetadata> columns, List<Field> fields) where T : class
{
Type type = typeof(T);
var member = propertyExpression.Body as MemberExpression;
if (member == null)
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a method, not a property.",
propertyExpression.ToString()));
var propInfo = member.Member as PropertyInfo;
if (propInfo == null)
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a field, not a property.",
propertyExpression.ToString()));
if (type != propInfo.ReflectedType &&
!type.IsSubclassOf(propInfo.ReflectedType))
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a property that is not from type {1}.",
propertyExpression.ToString(),
type));
var (dbFieldame, dbFieldType) = GetRdsFieldMapValues(propertyExpression, member);
if (string.IsNullOrEmpty(dbFieldame))
{
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a property that does not have an {1}.",
propertyExpression.ToString(),
nameof(RdsFieldMapAttribute)));
}
var columnIdx = columns.FindIndex(f => f.Name == dbFieldame);
if (columnIdx == -1)
{
throw new ArgumentException(string.Format(
"Expression '{0}' refers to a column name of {1} that does not exist.",
propertyExpression.ToString(),
dbFieldame));
}
var column = columns[columnIdx];
var setter = CreateSetter<T, TP>(propInfo.Name, propInfo.PropertyType);
var field = fields[columnIdx];
dynamic value = dbFieldType switch
{
RdsFieldType.Unknown => null,
RdsFieldType.ArrayValue => field.ArrayValue,
RdsFieldType.BlobValue => field.BlobValue,
RdsFieldType.BooleanValue => field.BooleanValue,
RdsFieldType.DoubleValue => field.DoubleValue,
RdsFieldType.IsNull => null,
RdsFieldType.LongValue => field.LongValue,
RdsFieldType.StringValue => field.StringValue,
_ => null,
};
switch (column.Type)
{
case ColumnTypes.TIMESTAMP:
value = DateTimeOffset.Parse(value);
break;
}
setter(entity, value);
return true;
}
private MemberExpression GetMemberExpression<T, TP>(Expression<Func<T, TP>> propertyExpression)
{
Expression expression;
if (propertyExpression.Body.NodeType == ExpressionType.Convert)
{
expression = ((UnaryExpression)propertyExpression.Body).Operand;
}
else
{
expression = propertyExpression.Body;
}
if (expression.NodeType != ExpressionType.MemberAccess)
{
throw new ArgumentException("Must be a property expression.", "propertyExpression");
}
return (MemberExpression)expression;
}
private static Action<TEntity, TProperty> CreateSetter<TEntity, TProperty>(string name, Type propType) where TEntity : class
{
ParameterExpression instance = Expression.Parameter(typeof(TEntity), "instance");
var propertyValue = Expression.Parameter(propType, "propertyValue");
var body = Expression.Assign(Expression.Property(instance, name), propertyValue);
return Expression.Lambda<Action<TEntity, TProperty>>(body, instance, propertyValue).Compile();
}
private (string dbFieldame, Services.AwsRds.Dtos.RdsFieldType dbFieldType) GetRdsFieldMapValues<T, TP>(Expression<Func<T, TP>> propertyExpression, MemberExpression memberExpression = null)
{
var me = memberExpression ?? GetMemberExpression(propertyExpression);
var member = me.Member;
var att = member.GetCustomAttributes(typeof(RdsFieldMapAttribute), false).OfType<RdsFieldMapAttribute>().FirstOrDefault();
if (att == null)
{
return (null, Services.AwsRds.Dtos.RdsFieldType.Unknown);
}
return (att.DbFieldName, att.FieldType);
}
}
{
"ColumnMetadata": [
{
"ArrayBaseColumnType": 0,
"IsAutoIncrement": true,
"IsCaseSensitive": false,
"IsCurrency": false,
"IsSigned": true,
"Label": "id",
"Name": "id",
"Nullable": 0,
"Precision": 11,
"Scale": 0,
"SchemaName": "",
"TableName": "EXAMPLES",
"Type": 4,
"TypeName": "INT"
},
{
"ArrayBaseColumnType": 0,
"IsAutoIncrement": false,
"IsCaseSensitive": false,
"IsCurrency": false,
"IsSigned": false,
"Label": "exampleId",
"Name": "exampleId",
"Nullable": 0,
"Precision": 64,
"Scale": 0,
"SchemaName": "",
"TableName": "EXAMPLES",
"Type": 12,
"TypeName": "VARCHAR"
},
{
"ArrayBaseColumnType": 0,
"IsAutoIncrement": false,
"IsCaseSensitive": false,
"IsCurrency": false,
"IsSigned": false,
"Label": "exampleName",
"Name": "exapleName",
"Nullable": 0,
"Precision": 200,
"Scale": 0,
"SchemaName": "",
"TableName": "EXAMPLES",
"Type": 12,
"TypeName": "VARCHAR"
},
{
"ArrayBaseColumnType": 0,
"IsAutoIncrement": false,
"IsCaseSensitive": false,
"IsCurrency": false,
"IsSigned": false,
"Label": "isActive",
"Name": "isActive",
"Nullable": 1,
"Precision": 1,
"Scale": 0,
"SchemaName": "",
"TableName": "EXAMPLES",
"Type": -7,
"TypeName": "BIT"
},
{
"ArrayBaseColumnType": 0,
"IsAutoIncrement": false,
"IsCaseSensitive": false,
"IsCurrency": false,
"IsSigned": false,
"Label": "isDeleted",
"Name": "isDeleted",
"Nullable": 1,
"Precision": 1,
"Scale": 0,
"SchemaName": "",
"TableName": "EXAMPLES",
"Type": -7,
"TypeName": "BIT"
},
{
"ArrayBaseColumnType": 0,
"IsAutoIncrement": false,
"IsCaseSensitive": false,
"IsCurrency": false,
"IsSigned": false,
"Label": "createdOn",
"Name": "createdOn",
"Nullable": 1,
"Precision": 19,
"Scale": 0,
"SchemaName": "",
"TableName": "EXAMPLES",
"Type": 93,
"TypeName": "TIMESTAMP"
},
{
"ArrayBaseColumnType": 0,
"IsAutoIncrement": false,
"IsCaseSensitive": false,
"IsCurrency": false,
"IsSigned": false,
"Label": "createdBy",
"Name": "createdBy",
"Nullable": 0,
"Precision": 100,
"Scale": 0,
"SchemaName": "",
"TableName": "EXAMPLES",
"Type": 12,
"TypeName": "VARCHAR"
},
{
"ArrayBaseColumnType": 0,
"IsAutoIncrement": false,
"IsCaseSensitive": false,
"IsCurrency": false,
"IsSigned": false,
"Label": "updatedOn",
"Name": "updatedOn",
"Nullable": 1,
"Precision": 19,
"Scale": 0,
"SchemaName": "",
"TableName": "EXAMPLES",
"Type": 93,
"TypeName": "TIMESTAMP"
},
{
"ArrayBaseColumnType": 0,
"IsAutoIncrement": false,
"IsCaseSensitive": false,
"IsCurrency": false,
"IsSigned": false,
"Label": "updatedBy",
"Name": "updatedBy",
"Nullable": 0,
"Precision": 100,
"Scale": 0,
"SchemaName": "",
"TableName": "EXAMPLES",
"Type": 12,
"TypeName": "VARCHAR"
}
],
"GeneratedFields": [],
"NumberOfRecordsUpdated": 0,
"Records": [
[
{
"ArrayValue": null,
"BlobValue": null,
"BooleanValue": false,
"DoubleValue": 0.0,
"IsNull": false,
"LongValue": 4,
"StringValue": null
},
{
"ArrayValue": null,
"BlobValue": null,
"BooleanValue": false,
"DoubleValue": 0.0,
"IsNull": false,
"LongValue": 0,
"StringValue": "63D224E7-3333-45A0-123A-4897BA169852"
},
{
"ArrayValue": null,
"BlobValue": null,
"BooleanValue": false,
"DoubleValue": 0.0,
"IsNull": false,
"LongValue": 0,
"StringValue": "Example 2"
},
{
"ArrayValue": null,
"BlobValue": null,
"BooleanValue": true,
"DoubleValue": 0.0,
"IsNull": false,
"LongValue": 0,
"StringValue": null
},
{
"ArrayValue": null,
"BlobValue": null,
"BooleanValue": false,
"DoubleValue": 0.0,
"IsNull": false,
"LongValue": 0,
"StringValue": null
},
{
"ArrayValue": null,
"BlobValue": null,
"BooleanValue": false,
"DoubleValue": 0.0,
"IsNull": false,
"LongValue": 0,
"StringValue": "2020-06-02 16:50:57"
},
{
"ArrayValue": null,
"BlobValue": null,
"BooleanValue": false,
"DoubleValue": 0.0,
"IsNull": false,
"LongValue": 0,
"StringValue": "[email protected]"
},
{
"ArrayValue": null,
"BlobValue": null,
"BooleanValue": false,
"DoubleValue": 0.0,
"IsNull": false,
"LongValue": 0,
"StringValue": "2020-06-02 16:50:57"
},
{
"ArrayValue": null,
"BlobValue": null,
"BooleanValue": false,
"DoubleValue": 0.0,
"IsNull": false,
"LongValue": 0,
"StringValue": "[email protected]"
}
],
[
{
"ArrayValue": null,
"BlobValue": null,
"BooleanValue": false,
"DoubleValue": 0.0,
"IsNull": false,
"LongValue": 4,
"StringValue": null
},
{
"ArrayValue": null,
"BlobValue": null,
"BooleanValue": false,
"DoubleValue": 0.0,
"IsNull": false,
"LongValue": 0,
"StringValue": "63D224E7-1878-45A0-9B4A-4897BA169852"
},
{
"ArrayValue": null,
"BlobValue": null,
"BooleanValue": false,
"DoubleValue": 0.0,
"IsNull": false,
"LongValue": 0,
"StringValue": "Example 1"
},
{
"ArrayValue": null,
"BlobValue": null,
"BooleanValue": true,
"DoubleValue": 0.0,
"IsNull": false,
"LongValue": 0,
"StringValue": null
},
{
"ArrayValue": null,
"BlobValue": null,
"BooleanValue": false,
"DoubleValue": 0.0,
"IsNull": false,
"LongValue": 0,
"StringValue": null
},
{
"ArrayValue": null,
"BlobValue": null,
"BooleanValue": false,
"DoubleValue": 0.0,
"IsNull": false,
"LongValue": 0,
"StringValue": "2020-06-02 16:50:57"
},
{
"ArrayValue": null,
"BlobValue": null,
"BooleanValue": false,
"DoubleValue": 0.0,
"IsNull": false,
"LongValue": 0,
"StringValue": "[email protected]"
},
{
"ArrayValue": null,
"BlobValue": null,
"BooleanValue": false,
"DoubleValue": 0.0,
"IsNull": false,
"LongValue": 0,
"StringValue": "2020-06-02 16:50:57"
},
{
"ArrayValue": null,
"BlobValue": null,
"BooleanValue": false,
"DoubleValue": 0.0,
"IsNull": false,
"LongValue": 0,
"StringValue": "[email protected]"
}
]
],
"ResponseMetadata": {
"RequestId": "0bd7242d-9bd0-4df6-a569-d20ed0715c48",
"Metadata": {}
},
"ContentLength": 3612,
"HttpStatusCode": 200
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment