Last active
June 4, 2020 03:23
-
-
Save d1820/b405f5bf1c2c1798b263ecba49fae919 to your computer and use it in GitHub Desktop.
AWS Aurora DataAPI expression mappers - .Net Core 3.1
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static class ColumnTypes | |
{ | |
public const int STRING = 12; | |
public const int BOOL = -7; | |
public const int TIMESTAMP = 93; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 } | |
}; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public interface IQueryResultMapper | |
{ | |
string MapToSelectString<T>(string selectPrefix = null, params Expression<Func<T, object>>[] expressions); | |
RdsExampleDto MapToExample(List<ColumnMetadata> columns, List<Field> fields); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[AttributeUsage(AttributeTargets.Property, Inherited = false)] | |
public class RdsFieldMapAttribute : Attribute | |
{ | |
public RdsFieldMapAttribute(string dbFieldName, RdsFieldType fieldType) | |
{ | |
DbFieldName = dbFieldName; | |
FieldType = fieldType; | |
} | |
public string DbFieldName { get; } | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"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