在C#中处理枚举与数据库映射的技巧

在开发过程中,经常需要将数据库中的整数值映射到C#中的枚举类型。尽管整数值是最简单的映射方式,但它并不是最直观的。例如,当处理包裹发送状态时,可能希望使用字符串值来表示状态,而不是数字。这样做的好处是代码更易于理解和维护。

假设有以下包裹发送状态的枚举:

public enum PackageStatus { RequestReceived = 0, Accepted = 1, Assembling = 2, Sended = 3, CustomerReceived = 4, Completed = 5 }

这是一个有效的包裹发送流程。对于处理这种机制的系统来说,这种流程可能很容易记住。但随着公司和软件的增长,可能会出现更多的枚举类型。因此,最好的方法是将字符串和枚举类型结合起来,并允许将字符串(如"0"和"RequestReceived")转换为枚举值。

此外,还可以添加一个功能,使得这种转换不区分大小写。虽然这不是必需的,但这是一个不错的特性。除了与数据库接口之外,还有其他用例,例如:

  • 用户界面输入
  • JSON序列化和反序列化
  • XML序列化
  • 从各种数据源(如CSV)导入

接下来,将讨论如何实现这些功能。首先,需要检索枚举类型的值:

var values = Enum.GetValues(typeof(TEnum));

这是一个简单的静态方法,它返回枚举的所有可能值。然后,可以使用foreach循环遍历这些值:

public static TEnum GetEnumValue(string val) { var values = Enum.GetValues(typeof(TEnum)); foreach (TEnum value in values) { if (val.Equals(value.ToString())) { return value; } } return default(TEnum); }

这种方法有几个问题。首先,只能使用一个预定义的枚举类型。遗憾的是,在C#中无法创建一个通用的枚举方法。但可以做得很接近。第二个问题是,不需要在枚举类型中使用默认值0。0根本不能在枚举中!

对于第一个问题,可以添加一个泛型类型参数,并约束它实现枚举类型接口,并使用结构特殊约束。对于第二个问题,可以使用C#的default关键字。改进后的方法声明如下:

public static TEnum GetEnumValue(string val) where TEnum : struct, IComparable, IFormattable, IConvertible { var values = Enum.GetValues(typeof(TEnum)); foreach (TEnum value in values) { if (val.Equals(value.ToString())) { return value; } } return default(TEnum); }

当然,还有其他类型可以使用这种方法,但它们实际上不是枚举类型。这是C#中最好的解决方案(据所知)。如果字符串值在方法中找不到,default语句将返回枚举的第一个定义值。

下一步是添加整数值的可能性。为此,需要将枚举值转换为int类型。不能使用C#中定义的泛型类型进行简单的转换操作,但可以使用方法IConvertible的ToInt32方法。这需要一个格式提供程序来进行转换。使用了CultureInfo.CurrentCulture属性,这在应用程序中是可以的,但在其他应用程序中可能会有问题。这取决于它将在哪里使用。改进后的方法如下:

public static TEnum GetEnumValue2(string val) where TEnum : struct, IComparable, IFormattable, IConvertible { var values = Enum.GetValues(typeof(TEnum)); foreach (TEnum value in values) { if (val.Equals(value.ToString()) || val == (value.ToInt32(CultureInfo.CurrentCulture)).ToString()) { return value; } } return default(TEnum); }

这基本上可以正常工作,但当它像这样使用时可能会有问题:

package.Status = GetEnumValue<PackageStatus>(newStatusString);

为什么?因为当newStatusString的值不是枚举的有效值时,status属性将重置为默认状态值。这可能是一个问题。解决方案可能是在值无效时抛出异常。这对于UI来说可能是好的。决定使用自定义默认值:

package.Status = GetEnumValue(newStatusString, package.Status);

这样,如果字符串中的值无效,状态将不会更改,旧值将被分配。

最后,为字符串比较添加了不区分大小写的功能。在.NET中有很多方法可以做到这一点,所以这应该根据将使用该代码的应用程序来考虑。例如,可以这样做:

public static TEnum GetEnumValue2(string val, TEnum current) where TEnum : struct, IComparable, IFormattable, IConvertible { var values = Enum.GetValues(typeof(TEnum)); foreach (TEnum value in values) { if (val.Equals(value.ToString(), StringComparison.OrdinalIgnoreCase) || val == (value.ToInt32(CultureInfo.CurrentCulture)).ToString()) { return value; } } return current; }

一个很好的特性是将这个方法定义为字符串的扩展方法。这样,可以在写变量名后调用它,该变量的值是字符串值。

package.Status = newStatusString.GetEnumValue(package.Status);

更喜欢这样做,因为这对于编码风格来说更具有表达性。在为某种问题编写解决方案时,会想:想要这个值,但在映射后以这种方式。使用GetEnumValue方法作为普通方法而不是扩展方法,在看来,对于阅读代码的人来说是一个更大的负担(这通常是,总是想让生活更轻松:))。但这是另一篇文章的主题。

无论如何,这可以通过添加这个关键字并放置方法在单独的类中来实现:

public static class Extension { public static TEnum GetEnumValue(this string val, TEnum current) where TEnum : struct, IComparable, IFormattable, IConvertible { var values = Enum.GetValues(typeof(TEnum)); foreach (TEnum value in values) { if (val.Equals(value.ToString(), StringComparison.OrdinalIgnoreCase) || val == (value.ToInt32(CultureInfo.CurrentCulture)).ToString()) { return value; } } return current; } }
沪ICP备2024098111号-1
上海秋旦网络科技中心:上海市奉贤区金大公路8218号1幢 联系电话:17898875485