16

Windows Azure 表存储不支持十进制数据类型。

建议的解决方法是使用自定义属性将小数属性序列化为字符串:

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

如何实现这个 EntityDataType 自定义属性,以便可以从 Windows Azure 表中存储和检索十进制属性?

4

5 回答 5

15

覆盖ReadEntityWriteEntity在基类中对此有好处。不必在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接口的类型。

于 2014-09-08T04:08:42.393 回答
6

您可以覆盖 TableEntity 中的 WriteEntity 方法并使用 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; }
}

包括创建表/插入/检索的用法

#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
于 2013-04-20T02:38:08.513 回答
2

您是否尝试过使用Lokad.Cloud FatEntities产品?

我认为他们只是对要存储在表中的整个对象使用二进制序列化程序。看看“Object-to-Cloud mapper”项目可能也是值得的:

https://github.com/Lokad/lokad-cloud

于 2012-06-17T19:57:33.230 回答
0

@EUYUIL 提出了一个很好的通用解决方案,我使用它效果很好,但是按照他的回答,使用 Nullable 类型时它会失败。

       // Get the underlying types 'Parse' method
       if (curType.IsGenericType && curType.GetGenericTypeDefinition() == typeof(Nullable<>))
       {
            curType = Nullable.GetUnderlyingType(curType);
       }

万一它对任何人有帮助,foreach 中的 ReadEntity 覆盖方法的内容。可能有更好的方法来写这个,但为了说明目的,这将是可行的。

        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);
于 2015-03-02T03:18:44.303 回答
0

您可以将属性的类型更改为doubledecimal然后,您必须double通过将表实体映射到您自己的域类型来进行转换。另一种选择是在由单个decimal字段支持的实体上具有两个属性。但是,您可能希望继续使用该属性的Quantity名称,decimal因为它是double存储在表中的属性,您必须Quantity通过覆盖ReadEntity和重命名该属性WriteEntity。那么你不妨使用这里提出的其他一些解决方案。

现在,您可能认为将 a 存储decimal为 adouble会导致某些值无法正确往返。虽然肯定有一些值不会因为两种类型的范围和精度非常不同而往返,但大多数“正常”值(例如不是天文数字大且具有人类可读精度的货币值)将毫无问题地往返。这样做的原因是,从doubletodecimal执行的转换Convert.ToDouble具有一个特殊的属性:

此方法返回的 Decimal 值最多包含 15 个有效数字。

以下是一个示例,说明如何将其他有问题的数字进行往返:

var originalValue = 2.24M;
var doubleValue = (double) originalValue;

问题是没有使用浮点数的十进制数 2.24 的精确表示,就像没有使用十进制数的有理数 1/3 的精确表示一样(2.24 是有理数 224/100)。0.3333333333333333 不等于 1/3。doubleValue您可以通过足够精确的打印来验证这一点。Console.WriteLine($"{doubleValue:G17}")产量

2.2400000000000002

但是,往返值仍然有效:

var roundTripValue = (decimal) doubleValue;

现在Console.WriteLine(roundTripValue)产量

2.24

因此,只要您不对这些double值进行任何计算,您就可以使用它们来存储decimal值,前提是之间的转换double符合decimal上面引用的 .NET 规则。

于 2018-05-12T13:42:19.327 回答