Skip to content
This repository was archived by the owner on Dec 24, 2022. It is now read-only.

Commit 9ffb914

Browse files
committed
Add support for [EnumAsChar] enums to store enums with their char value
1 parent f5d6035 commit 9ffb914

File tree

4 files changed

+143
-14
lines changed

4 files changed

+143
-14
lines changed

src/ServiceStack.OrmLite/Converters/SpecialConverters.cs

Lines changed: 73 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,48 @@
1010

1111
namespace ServiceStack.OrmLite.Converters
1212
{
13+
public enum EnumKind
14+
{
15+
String,
16+
Int,
17+
Char
18+
}
19+
1320
public class EnumConverter : StringConverter
1421
{
1522
public EnumConverter() : base(255) {}
1623

24+
static Dictionary<Type, EnumKind> enumTypeCache = new Dictionary<Type, EnumKind>();
25+
26+
public static EnumKind GetEnumKind(Type enumType)
27+
{
28+
if (enumTypeCache.TryGetValue(enumType, out var enumKind))
29+
return enumKind;
30+
31+
enumKind = IsIntEnum(enumType)
32+
? EnumKind.Int
33+
: enumType.HasAttributeCached<EnumAsCharAttribute>()
34+
? EnumKind.Char
35+
: EnumKind.String;
36+
37+
Dictionary<Type, EnumKind> snapshot, newCache;
38+
do
39+
{
40+
snapshot = enumTypeCache;
41+
newCache = new Dictionary<Type, EnumKind>(enumTypeCache) {
42+
[enumType] = enumKind
43+
};
44+
} while (!ReferenceEquals(
45+
System.Threading.Interlocked.CompareExchange(ref enumTypeCache, newCache, snapshot), snapshot));
46+
47+
return enumKind;
48+
}
49+
1750
public override void InitDbParam(IDbDataParameter p, Type fieldType)
1851
{
19-
var isIntEnum = IsIntEnum(fieldType);
52+
var enumKind = GetEnumKind(fieldType);
2053

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

2861
public override string ToQuotedString(Type fieldType, object value)
2962
{
30-
var isEnumAsInt = fieldType.HasAttributeCached<EnumAsIntAttribute>();
31-
if (isEnumAsInt)
63+
var enumKind = GetEnumKind(fieldType);
64+
if (enumKind == EnumKind.Int)
3265
return this.ConvertNumber(Enum.GetUnderlyingType(fieldType), value).ToString();
3366

67+
if (enumKind == EnumKind.Char)
68+
return DialectProvider.GetQuotedValue(ToCharValue(value).ToString());
69+
3470
var isEnumFlags = fieldType.IsEnumFlags() ||
3571
(!fieldType.IsEnum && fieldType.IsNumericType()); //i.e. is real int && not Enum
3672

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

4985
public override object ToDbValue(Type fieldType, object value)
5086
{
51-
var isIntEnum = IsIntEnum(fieldType);
87+
var enumKind = GetEnumKind(fieldType);
5288

53-
if (isIntEnum && value.GetType().IsEnum)
54-
return Convert.ChangeType(value, Enum.GetUnderlyingType(fieldType));
89+
if (value.GetType().IsEnum)
90+
{
91+
if (enumKind == EnumKind.Int)
92+
return Convert.ChangeType(value, Enum.GetUnderlyingType(fieldType));
93+
94+
if (enumKind == EnumKind.Char)
95+
return Convert.ChangeType(value, typeof(char));
96+
}
97+
98+
if (enumKind == EnumKind.Char)
99+
{
100+
var charValue = ToCharValue(value);
101+
return charValue;
102+
}
55103

56104
if (long.TryParse(value.ToString(), out var enumValue))
57105
{
58-
if (isIntEnum)
106+
if (enumKind == EnumKind.Int)
59107
return enumValue;
60108

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

118+
public static char ToCharValue(object value)
119+
{
120+
var charValue = value is char c
121+
? c
122+
: value is string s && s.Length == 1
123+
? s[0]
124+
: value is int i
125+
? (char) i
126+
: (char) Convert.ChangeType(value, typeof(char));
127+
return charValue;
128+
}
129+
70130
//cache expensive to calculate operation
71131
static readonly ConcurrentDictionary<Type, bool> intEnums = new ConcurrentDictionary<Type, bool>();
72132

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

84144
public override object FromDbValue(Type fieldType, object value)
85145
{
146+
var enumKind = GetEnumKind(fieldType);
147+
148+
if (enumKind == EnumKind.Char)
149+
return Enum.ToObject(fieldType, (int)ToCharValue(value));
150+
86151
if (value is string strVal)
87152
return Enum.Parse(fieldType, strVal, ignoreCase:true);
88153

src/ServiceStack.OrmLite/Converters/StringConverter.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ public override void InitDbParam(IDbDataParameter p, Type fieldType)
4040
{
4141
base.InitDbParam(p, fieldType);
4242

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

8585
return (char)value;
8686
}
87+
88+
public override object ToDbValue(Type fieldType, object value)
89+
{
90+
if (value != null && value.GetType().IsEnum)
91+
return EnumConverter.ToCharValue(value);
92+
if (value is int i)
93+
return (char)i;
94+
95+
return base.ToDbValue(fieldType, value);
96+
}
8797
}
8898

8999
public class CharArrayConverter : StringConverter

src/ServiceStack.OrmLite/OrmLiteConfigExtensions.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@
1111

1212
using System;
1313
using System.Collections.Generic;
14+
using System.ComponentModel;
1415
using System.Linq;
1516
using System.Reflection;
1617
using System.Threading;
1718
using ServiceStack.DataAnnotations;
19+
using ServiceStack.OrmLite.Converters;
1820

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

109112
Type treatAsType = null;
110-
if (propertyType.IsEnumFlags() || propertyType.HasAttributeCached<EnumAsIntAttribute>())
111-
treatAsType = Enum.GetUnderlyingType(propertyType);
112-
else if (propertyType.HasAttributeCached<EnumAsCharAttribute>())
113-
treatAsType = typeof(char);
113+
114+
if (propertyType.IsEnum)
115+
{
116+
var enumKind = Converters.EnumConverter.GetEnumKind(propertyType);
117+
if (enumKind == EnumKind.Int)
118+
treatAsType = Enum.GetUnderlyingType(propertyType);
119+
else if (enumKind == EnumKind.Char)
120+
treatAsType = typeof(char);
121+
}
114122

115123
var isReference = referenceAttr != null && propertyType.IsClass;
116124
var isIgnored = propertyInfo.HasAttributeCached<IgnoreAttribute>() || isReference;

tests/ServiceStack.OrmLite.Tests/EnumTests.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Linq;
33
using NUnit.Framework;
44
using ServiceStack.DataAnnotations;
5+
using ServiceStack.Logging;
56
using ServiceStack.Text;
67

78
namespace ServiceStack.OrmLite.Tests
@@ -212,6 +213,7 @@ public void Updates_enum_flags_with_int_value()
212213
[Test]
213214
public void Updates_EnumAsInt_with_int_value()
214215
{
216+
OrmLiteUtils.PrintSql();
215217
using (var db = OpenDbConnection())
216218
{
217219
db.DropAndCreateTable<TypeWithEnumAsInt>();
@@ -232,6 +234,35 @@ public void Updates_EnumAsInt_with_int_value()
232234
Assert.That(row.EnumValue, Is.EqualTo(SomeEnumAsInt.Value3));
233235
}
234236
}
237+
238+
[Test]
239+
public void Updates_EnumAsChar_with_char_value()
240+
{
241+
OrmLiteUtils.PrintSql();
242+
using (var db = OpenDbConnection())
243+
{
244+
db.DropAndCreateTable<TypeWithEnumAsChar>();
245+
Assert.That(db.GetLastSql().ToUpper().IndexOf("CHAR(1)", StringComparison.Ordinal) >= 0);
246+
247+
db.Insert(new TypeWithEnumAsChar { Id = 1, EnumValue = CharEnum.Value1 });
248+
db.Insert(new TypeWithEnumAsChar { Id = 2, EnumValue = CharEnum.Value2 });
249+
db.Insert(new TypeWithEnumAsChar { Id = 3, EnumValue = CharEnum.Value3 });
250+
251+
var row = db.SingleById<TypeWithEnumAsChar>(1);
252+
Assert.That(row.EnumValue, Is.EqualTo(CharEnum.Value1));
253+
254+
db.Update(new TypeWithEnumAsChar { Id = 1, EnumValue = CharEnum.Value1 });
255+
Assert.That(db.GetLastSql(), Does.Contain("=@EnumValue").Or.Contain("=:EnumValue"));
256+
db.GetLastSql().Print();
257+
258+
db.UpdateOnly(new TypeWithEnumAsChar { Id = 1, EnumValue = CharEnum.Value3 },
259+
onlyFields: q => q.EnumValue);
260+
Assert.That(db.GetLastSql().NormalizeSql(), Does.Contain("=@enumvalue"));
261+
262+
row = db.SingleById<TypeWithEnumAsChar>(1);
263+
Assert.That(row.EnumValue, Is.EqualTo(CharEnum.Value3));
264+
}
265+
}
235266

236267
[Test]
237268
public void CanQueryByEnumValue_using_select_with_expression_enum_flags()
@@ -375,7 +406,7 @@ public void Does_save_Enum_with_label_by_default()
375406
[Test]
376407
public void Can_save_Enum_as_Integers()
377408
{
378-
using (JsConfig.With(treatEnumAsInteger: true))
409+
using (JsConfig.With(new Config { TreatEnumAsInteger = true }))
379410
using (var db = OpenDbConnection())
380411
{
381412
db.DropAndCreateTable<TypeWithTreatEnumAsInt>();
@@ -475,6 +506,14 @@ public void Can_select_enum_using_tuple()
475506
}
476507
}
477508

509+
[EnumAsChar]
510+
public enum CharEnum : int
511+
{
512+
Value1 = 'A',
513+
Value2 = 'B',
514+
Value3 = 'C',
515+
Value4 = 'D'
516+
}
478517

479518
public class DoubleState
480519
{
@@ -537,6 +576,13 @@ public enum SomeEnumAsInt
537576
Value3 = 3,
538577
}
539578

579+
public class TypeWithEnumAsChar
580+
{
581+
public int Id { get; set; }
582+
583+
public CharEnum EnumValue { get; set; }
584+
}
585+
540586
public class TypeWithEnumAsInt
541587
{
542588
public int Id { get; set; }

0 commit comments

Comments
 (0)