简体   繁体   English

在Azure表中存储十进制数据类型

[英]Storing decimal data type in Azure Tables

Windows Azure Table Storage does not support the decimal data type. Windows Azure表存储不支持 十进制数据类型。

A suggested workaround is to use a custom attribute to serialize the decimal property as string: 建议的解决方法是使用自定义属性将decimal属性序列化为字符串:

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

How could this EntityDataType custom attribute be implemented so decimal properties can be stored and retrieved from Windows Azure Tables? 如何实现此EntityDataType自定义属性,以便可以从Windows Azure表存储和检索十进制属性?

Overriding ReadEntity and WriteEntity in the base class is good for this. 覆盖基类中的ReadEntityWriteEntity对此有好处。 It's not necessary to write an EntityResolver every time you retrieve the entities. 每次检索实体时都没有必要编写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;
    }
}

When you use, just make your entities extend from CustomTableEntity and it will be transparent when inserting or retrieving entities. 使用时,只需从CustomTableEntity扩展实体,插入或检索实体时它将是透明的。 It supports DateTime , TimeSpan , decimal and those types who have Parse method or implement IConvertible interfaces. 它支持DateTimeTimeSpandecimal和那些具有Parse方法或实现IConvertible接口的类型。

You may override the WriteEntity method in TableEntity and use EntityResolver 您可以覆盖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;
    }
}

the entity we will use 我们将使用的实体

public class MyEntity : CustomTableEntity
{
    public string MyProperty { get; set; }

    public decimal MyDecimalProperty1 { get; set; }
    public decimal MyDecimalProperty2 { get; set; }
}

usage which includes Create Table / Insert / Retreive 用法包括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

Have you tried using the Lokad.Cloud FatEntities product? 您是否尝试过使用Lokad.Cloud FatEntities产品?

I think they are just using binary serializer for the whole object you want to store in the Table. 我认为他们只是使用二进制序列化器来处理要存储在表中的整个对象。 It might be worthwhile to take a look at the "Object-to-Cloud mapper" project also: 看看“对象到云映射器”项目也是值得的:

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

@EUYUIL has raised a good generic solution which I have used to good effect, however as his answer stands it will fail when using a Nullable type. @EUYUIL已经提出了一个很好的通用解决方案,我已经习惯了很好的效果,但是当他的回答表明它在使用Nullable类型时会失败。

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

In case it helps anyone, the contents of the ReadEntity override method inside the foreach. 如果它帮助任何人,readEntity的内容覆盖foreach内部的方法。 There may be better ways to write this, but for illustration purposes this will do. 可能有更好的方法来写这个,但为了说明的目的,这将做。

        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);

You can change the type of the property to double . 您可以将属性的类型更改为double You would then have to convert between decimal and double by mapping the table entity to your own domain type. 然后,您必须通过将表实体映射到您自己的域类型来在decimaldouble之间进行转换。 Another option would be to have two properties on the entity backed by a single decimal field. 另一种选择是在由单个decimal字段支持的实体上具有两个属性。 However, you probably want to keep using the Quantity name for the decimal property and as it is the double property that is be stored in the table you will have to rename this property to Quantity by overriding ReadEntity and WriteEntity . 但是,您可能希望继续使用decimal属性的Quantity名称,因为它是存储在表中的double属性,您必须通过重写ReadEntityWriteEntity将此属性重命名为Quantity Then you might as well use some of the other solutions proposed here. 那么你不妨使用这里提出的其他一些解决方案。

Now, you might think that storing a decimal as a double leads to some values not round-tripping correctly. 现在,您可能认为将decimal存储为double会导致某些值无法正确地进行往返。 While there certainly are values that will not round-trip simply because the range and precision of the two types are very different most "normal" values like monetary values that are not astronomically large and with human readable precision will round-trip without any problems. 虽然肯定存在不会往返的值,因为两种类型的范围和精度是非常不同的,大多数“正常”值,例如货币值不是天文数字大且具有人类可读的精度,将会没有任何问题地往返。 The reason for this is that conversion from double to decimal as performed by Convert.ToDouble has a special property: 原因是Convert.ToDouble执行的从doubledecimal转换具有一个特殊属性:

The Decimal value returned by this method contains a maximum of 15 significant digits. 此方法返回的Decimal值最多包含15位有效数字。

Here is an example of how round-tripping an otherwise problematic number works because of this: 下面是一个例子,说明如何解决其他有问题的数字,因为这样:

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

The problem is that there is no exact representation of the decimal number 2.24 using floating point just as there is no exact representation of the rational number 1/3 using a decimal number (2.24 is the rational number 224/100). 问题是使用浮点没有精确表示十进制数2.24,因为没有使用十进制数(2.24是有理数224/100)的有理数1/3的精确表示。 0.3333333333333333 is not the same as 1/3. 0.3333333333333333与1/3不同。 You can verify this by printing doubleValue with enough precision. 您可以通过以足够的精度打印doubleValue来验证这一点。 Console.WriteLine($"{doubleValue:G17}") yields Console.WriteLine($"{doubleValue:G17}")产生

2.2400000000000002

However, round-tripping the value still works: 但是,绕过该值仍然有效:

var roundTripValue = (decimal) doubleValue;

Now Console.WriteLine(roundTripValue) yields 现在Console.WriteLine(roundTripValue)产生

2.24

So as long as you don't do any computations on the double values you can use them to store decimal values provided that the conversion between double and decimal adheres to the .NET rule cited above. 因此,只要您不对double值进行任何计算,就可以使用它们来存储decimal值,前提是doubledecimal之间的转换符合上面引用的.NET规则。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM