Сохранение десятичных данных в таблицах Azure
Windows Azure Table Storage не поддерживает тип данных десятичный.
A предлагаемое обходное решение заключается в использовании настраиваемого атрибута для сериализации десятичного свойства как строки:
[EntityDataType(PrimitiveTypeKind.String)]
public decimal Quantity { get; set; }
Как можно реализовать этот настраиваемый атрибут EntityDataType, поэтому десятичные свойства можно сохранить и извлечь из таблиц Windows Azure?
Ответы
Ответ 1
Вы можете переопределить метод WriteEntity в TableEntity и использовать EntityResolver
public class CustomTableEntity : TableEntity
{
private const string DecimalPrefix = "D_";
public override IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext)
{
var entityProperties = base.WriteEntity(operationContext);
var objectProperties = GetType().GetProperties();
foreach (var item in objectProperties.Where(f => f.PropertyType == typeof (decimal)))
{
entityProperties.Add(DecimalPrefix + item.Name, new EntityProperty(item.GetValue(this, null).ToString()));
}
return entityProperties;
}
}
объект, который мы будем использовать
public class MyEntity : CustomTableEntity
{
public string MyProperty { get; set; }
public decimal MyDecimalProperty1 { get; set; }
public decimal MyDecimalProperty2 { get; set; }
}
использование, которое включает Create Table/Insert/Retreive
#region connection
CloudStorageAccount account = CloudStorageAccount.DevelopmentStorageAccount;
CloudTableClient client = account.CreateCloudTableClient();
CloudTable table = client.GetTableReference("mytable");
table.CreateIfNotExists();
#endregion
const string decimalPrefix = "D_";
const string partitionKey = "BlaBlaBla";
string rowKey = DateTime.Now.ToString("yyyyMMddHHmmss");
#region Insert
var entity = new MyEntity
{
PartitionKey = partitionKey,
RowKey = rowKey,
MyProperty = "Test",
MyDecimalProperty1 = (decimal) 1.2,
MyDecimalProperty2 = (decimal) 3.45
};
TableOperation insertOperation = TableOperation.Insert(entity);
table.Execute(insertOperation);
#endregion
#region Retrieve
EntityResolver<MyEntity> myEntityResolver = (pk, rk, ts, props, etag) =>
{
var resolvedEntity = new MyEntity {PartitionKey = pk, RowKey = rk, Timestamp = ts, ETag = etag};
foreach (var item in props.Where(p => p.Key.StartsWith(decimalPrefix)))
{
string realPropertyName = item.Key.Substring(decimalPrefix.Length);
System.Reflection.PropertyInfo propertyInfo = resolvedEntity.GetType().GetProperty(realPropertyName);
propertyInfo.SetValue(resolvedEntity, Convert.ChangeType(item.Value.StringValue, propertyInfo.PropertyType), null);
}
resolvedEntity.ReadEntity(props, null);
return resolvedEntity;
};
TableOperation retrieveOperation = TableOperation.Retrieve(partitionKey, rowKey, myEntityResolver);
TableResult retrievedResult = table.Execute(retrieveOperation);
var myRetrievedEntity = retrievedResult.Result as MyEntity;
// myRetrievedEntity.Dump();
#endregion
Ответ 2
Для этого полезно переопределение ReadEntity
и WriteEntity
в базовом классе. Не нужно писать EntityResolver
каждый раз, когда вы извлекаете объекты.
public class CustomTableEntity : TableEntity
{
public override void ReadEntity(IDictionary<string, EntityProperty> properties, OperationContext operationContext)
{
base.ReadEntity(properties, operationContext);
foreach (var thisProperty in
GetType().GetProperties().Where(thisProperty =>
thisProperty.GetType() != typeof(string) &&
properties.ContainsKey(thisProperty.Name) &&
properties[thisProperty.Name].PropertyType == EdmType.String))
{
var parse = thisProperty.PropertyType.GetMethods().SingleOrDefault(m =>
m.Name == "Parse" &&
m.GetParameters().Length == 1 &&
m.GetParameters()[0].ParameterType == typeof(string));
var value = parse != null ?
parse.Invoke(thisProperty, new object[] { properties[thisProperty.Name].StringValue }) :
Convert.ChangeType(properties[thisProperty.Name].PropertyAsObject, thisProperty.PropertyType);
thisProperty.SetValue(this, value);
}
}
public override IDictionary<string, EntityProperty> WriteEntity(OperationContext operationContext)
{
var properties = base.WriteEntity(operationContext);
foreach (var thisProperty in
GetType().GetProperties().Where(thisProperty =>
!properties.ContainsKey(thisProperty.Name) &&
typeof(TableEntity).GetProperties().All(p => p.Name != thisProperty.Name)))
{
var value = thisProperty.GetValue(this);
if (value != null)
{
properties.Add(thisProperty.Name, new EntityProperty(value.ToString()));
}
}
return properties;
}
}
Когда вы используете, просто сделайте свои сущности от CustomTableEntity
, и он будет прозрачным при вставке или извлечении объектов. Он поддерживает DateTime
, TimeSpan
, decimal
и те типы, которые имеют метод Parse
или реализуют интерфейсы IConvertible
.
Ответ 3
Вы пытались использовать продукт Lokad.Cloud FatEntities?
Я думаю, что они просто используют двоичный сериализатор для всего объекта, который вы хотите сохранить в таблице. Возможно, стоит также взглянуть на проект "Map-Map-Object-to-Cloud":
https://github.com/Lokad/lokad-cloud
Ответ 4
@EUYUIL поднял хорошее общее решение, которое я использовал для хорошего эффекта, однако, как говорит его ответ, он будет терпеть неудачу при использовании типа Nullable.
// Get the underlying types 'Parse' method
if (curType.IsGenericType && curType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
curType = Nullable.GetUnderlyingType(curType);
}
В случае, если это помогает кому-либо, содержимое метода переопределения ReadEntity внутри foreach. Там могут быть лучшие способы написать это, но для иллюстрации это будет делать.
var curType = thisProperty.PropertyType;
// Get the underlying types 'Parse' method
if (curType.IsGenericType && curType.GetGenericTypeDefinition() == typeof(Nullable<>))
{
curType = Nullable.GetUnderlyingType(curType);
}
var parse = curType.GetMethods().SingleOrDefault(m =>
m.Name == "Parse" &&
m.GetParameters().Length == 1 &&
m.GetParameters()[0].ParameterType == typeof(string));
var value = parse != null ?
parse.Invoke(thisProperty, new object[] { properties[thisProperty.Name].StringValue }) :
Convert.ChangeType(properties[thisProperty.Name].PropertyAsObject, thisProperty.PropertyType);
thisProperty.SetValue(this, value);