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

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

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

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

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

您可以覆盖TableEntity中的WriteEntity方法并使用EntityResolver

 public class CustomTableEntity : TableEntity { private const string DecimalPrefix = "D_"; public override IDictionary 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 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 

覆盖基类中的ReadEntityWriteEntity对此有好处。 每次检索实体时都没有必要编写EntityResolver

 public class CustomTableEntity : TableEntity { public override void ReadEntity(IDictionary 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 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扩展,并在插入或检索实体时保持透明。 它支持DateTimeTimeSpandecimal和那些具有Parse方法或实现IConvertible接口的类型。

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

我认为他们只是使用二进制序列化器来处理要存储在表中的整个对象。 看看“对象到云映射器”项目也是值得的:

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

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

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

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

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

下面是一个示例,说明如何绕过一个有问题的数字,因为这样:

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

问题是使用浮点没有精确表示十进制数2.24,因为没有使用十进制数(2.24是有理数224/100)的有理数1/3的精确表示。 0.3333333333333333与1/3不同。 您可以通过以足够的精度打印doubleValue来validation这一点。 Console.WriteLine($"{doubleValue:G17}")产生

 2.2400000000000002 

但是,绕过该值仍然有效:

 var roundTripValue = (decimal) doubleValue; 

现在Console.WriteLine(roundTripValue)产生

 2.24 

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