Skip to content
Permalink
Browse files

Add support for [EnumAsChar] enums to store enums with their char value

  • Loading branch information...
mythz committed Jun 6, 2019
1 parent f5d6035 commit 9ffb9147b0402b39c033098a7b24a376782dcdbb
@@ -10,15 +10,48 @@

namespace ServiceStack.OrmLite.Converters
{
public enum EnumKind
{
String,
Int,
Char
}

public class EnumConverter : StringConverter
{
public EnumConverter() : base(255) {}

static Dictionary<Type, EnumKind> enumTypeCache = new Dictionary<Type, EnumKind>();

public static EnumKind GetEnumKind(Type enumType)
{
if (enumTypeCache.TryGetValue(enumType, out var enumKind))
return enumKind;

enumKind = IsIntEnum(enumType)
? EnumKind.Int
: enumType.HasAttributeCached<EnumAsCharAttribute>()
? EnumKind.Char
: EnumKind.String;

Dictionary<Type, EnumKind> snapshot, newCache;
do
{
snapshot = enumTypeCache;
newCache = new Dictionary<Type, EnumKind>(enumTypeCache) {
[enumType] = enumKind
};
} while (!ReferenceEquals(
System.Threading.Interlocked.CompareExchange(ref enumTypeCache, newCache, snapshot), snapshot));

return enumKind;
}

public override void InitDbParam(IDbDataParameter p, Type fieldType)
{
var isIntEnum = IsIntEnum(fieldType);
var enumKind = GetEnumKind(fieldType);

p.DbType = isIntEnum
p.DbType = enumKind == EnumKind.Int
? Enum.GetUnderlyingType(fieldType) == typeof(long)
? DbType.Int64
: DbType.Int32
@@ -27,10 +60,13 @@ public override void InitDbParam(IDbDataParameter p, Type fieldType)

public override string ToQuotedString(Type fieldType, object value)
{
var isEnumAsInt = fieldType.HasAttributeCached<EnumAsIntAttribute>();
if (isEnumAsInt)
var enumKind = GetEnumKind(fieldType);
if (enumKind == EnumKind.Int)
return this.ConvertNumber(Enum.GetUnderlyingType(fieldType), value).ToString();

if (enumKind == EnumKind.Char)
return DialectProvider.GetQuotedValue(ToCharValue(value).ToString());

var isEnumFlags = fieldType.IsEnumFlags() ||
(!fieldType.IsEnum && fieldType.IsNumericType()); //i.e. is real int && not Enum

@@ -48,14 +84,26 @@ public override string ToQuotedString(Type fieldType, object value)

public override object ToDbValue(Type fieldType, object value)
{
var isIntEnum = IsIntEnum(fieldType);
var enumKind = GetEnumKind(fieldType);

if (isIntEnum && value.GetType().IsEnum)
return Convert.ChangeType(value, Enum.GetUnderlyingType(fieldType));
if (value.GetType().IsEnum)
{
if (enumKind == EnumKind.Int)
return Convert.ChangeType(value, Enum.GetUnderlyingType(fieldType));

if (enumKind == EnumKind.Char)
return Convert.ChangeType(value, typeof(char));
}

if (enumKind == EnumKind.Char)
{
var charValue = ToCharValue(value);
return charValue;
}

if (long.TryParse(value.ToString(), out var enumValue))
{
if (isIntEnum)
if (enumKind == EnumKind.Int)
return enumValue;

value = Enum.ToObject(fieldType, enumValue);
@@ -67,6 +115,18 @@ public override object ToDbValue(Type fieldType, object value)
: value.ToString();
}

public static char ToCharValue(object value)
{
var charValue = value is char c
? c
: value is string s && s.Length == 1
? s[0]
: value is int i
? (char) i
: (char) Convert.ChangeType(value, typeof(char));
return charValue;
}

//cache expensive to calculate operation
static readonly ConcurrentDictionary<Type, bool> intEnums = new ConcurrentDictionary<Type, bool>();

@@ -83,6 +143,11 @@ public static bool IsIntEnum(Type fieldType)

public override object FromDbValue(Type fieldType, object value)
{
var enumKind = GetEnumKind(fieldType);

if (enumKind == EnumKind.Char)
return Enum.ToObject(fieldType, (int)ToCharValue(value));

if (value is string strVal)
return Enum.Parse(fieldType, strVal, ignoreCase:true);

@@ -40,7 +40,7 @@ public override void InitDbParam(IDbDataParameter p, Type fieldType)
{
base.InitDbParam(p, fieldType);

if (p.Size == default)
if (p.Size == default && fieldType == typeof(string))
{
p.Size = UseUnicode
? Math.Min(StringLength, 4000)
@@ -84,6 +84,16 @@ public override object FromDbValue(Type fieldType, object value)

return (char)value;
}

public override object ToDbValue(Type fieldType, object value)
{
if (value != null && value.GetType().IsEnum)
return EnumConverter.ToCharValue(value);
if (value is int i)
return (char)i;

return base.ToDbValue(fieldType, value);
}
}

public class CharArrayConverter : StringConverter
@@ -11,10 +11,12 @@

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Threading;
using ServiceStack.DataAnnotations;
using ServiceStack.OrmLite.Converters;

namespace ServiceStack.OrmLite
{
@@ -105,12 +107,18 @@ internal static ModelDefinition GetModelDefinition(this Type modelType)
var propertyType = isNullableType
? Nullable.GetUnderlyingType(propertyInfo.PropertyType)
: propertyInfo.PropertyType;


Type treatAsType = null;
if (propertyType.IsEnumFlags() || propertyType.HasAttributeCached<EnumAsIntAttribute>())
treatAsType = Enum.GetUnderlyingType(propertyType);
else if (propertyType.HasAttributeCached<EnumAsCharAttribute>())
treatAsType = typeof(char);

if (propertyType.IsEnum)
{
var enumKind = Converters.EnumConverter.GetEnumKind(propertyType);
if (enumKind == EnumKind.Int)
treatAsType = Enum.GetUnderlyingType(propertyType);
else if (enumKind == EnumKind.Char)
treatAsType = typeof(char);
}

var isReference = referenceAttr != null && propertyType.IsClass;
var isIgnored = propertyInfo.HasAttributeCached<IgnoreAttribute>() || isReference;
@@ -2,6 +2,7 @@
using System.Linq;
using NUnit.Framework;
using ServiceStack.DataAnnotations;
using ServiceStack.Logging;
using ServiceStack.Text;

namespace ServiceStack.OrmLite.Tests
@@ -212,6 +213,7 @@ public void Updates_enum_flags_with_int_value()
[Test]
public void Updates_EnumAsInt_with_int_value()
{
OrmLiteUtils.PrintSql();
using (var db = OpenDbConnection())
{
db.DropAndCreateTable<TypeWithEnumAsInt>();
@@ -232,6 +234,35 @@ public void Updates_EnumAsInt_with_int_value()
Assert.That(row.EnumValue, Is.EqualTo(SomeEnumAsInt.Value3));
}
}

[Test]
public void Updates_EnumAsChar_with_char_value()
{
OrmLiteUtils.PrintSql();
using (var db = OpenDbConnection())
{
db.DropAndCreateTable<TypeWithEnumAsChar>();
Assert.That(db.GetLastSql().ToUpper().IndexOf("CHAR(1)", StringComparison.Ordinal) >= 0);

db.Insert(new TypeWithEnumAsChar { Id = 1, EnumValue = CharEnum.Value1 });
db.Insert(new TypeWithEnumAsChar { Id = 2, EnumValue = CharEnum.Value2 });
db.Insert(new TypeWithEnumAsChar { Id = 3, EnumValue = CharEnum.Value3 });

var row = db.SingleById<TypeWithEnumAsChar>(1);
Assert.That(row.EnumValue, Is.EqualTo(CharEnum.Value1));

db.Update(new TypeWithEnumAsChar { Id = 1, EnumValue = CharEnum.Value1 });
Assert.That(db.GetLastSql(), Does.Contain("=@EnumValue").Or.Contain("=:EnumValue"));
db.GetLastSql().Print();

db.UpdateOnly(new TypeWithEnumAsChar { Id = 1, EnumValue = CharEnum.Value3 },
onlyFields: q => q.EnumValue);
Assert.That(db.GetLastSql().NormalizeSql(), Does.Contain("=@enumvalue"));

row = db.SingleById<TypeWithEnumAsChar>(1);
Assert.That(row.EnumValue, Is.EqualTo(CharEnum.Value3));
}
}

[Test]
public void CanQueryByEnumValue_using_select_with_expression_enum_flags()
@@ -375,7 +406,7 @@ public void Does_save_Enum_with_label_by_default()
[Test]
public void Can_save_Enum_as_Integers()
{
using (JsConfig.With(treatEnumAsInteger: true))
using (JsConfig.With(new Config { TreatEnumAsInteger = true }))
using (var db = OpenDbConnection())
{
db.DropAndCreateTable<TypeWithTreatEnumAsInt>();
@@ -475,6 +506,14 @@ public void Can_select_enum_using_tuple()
}
}

[EnumAsChar]
public enum CharEnum : int
{
Value1 = 'A',
Value2 = 'B',
Value3 = 'C',
Value4 = 'D'
}

public class DoubleState
{
@@ -537,6 +576,13 @@ public enum SomeEnumAsInt
Value3 = 3,
}

public class TypeWithEnumAsChar
{
public int Id { get; set; }

public CharEnum EnumValue { get; set; }
}

public class TypeWithEnumAsInt
{
public int Id { get; set; }

0 comments on commit 9ffb914

Please sign in to comment.
You can’t perform that action at this time.