Подтвердить что ты не робот

Сохранение десятичных данных в таблицах Azure

Windows Azure Table Storage не поддерживает тип данных десятичный.

A предлагаемое обходное решение заключается в использовании настраиваемого атрибута для сериализации десятичного свойства как строки:

[EntityDataType(PrimitiveTypeKind.String)]
public decimal Quantity { get; set; }

Как можно реализовать этот настраиваемый атрибут EntityDataType, поэтому десятичные свойства можно сохранить и извлечь из таблиц Windows Azure?

4b9b3361

Ответ 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);