First, here is why this question is not a duplicate:
I know that multiple questions about converting one enum
to another have been asked on SO, I even answered one of them myself, but all the questions I've found on this topic had some way of comparing the different enum values (whether by name or by value). In my particular case, I don't know the values, and the names don't match.
As a part of a GitHub project I'm working on, called ADONETHelper, that's designed to minimize code repetition when working with Ado.Net, I came face to face with the need to translate values between unrelated enums. This is to allow the same code to work with OleDb, Odbc, and SqlClient (and hopefully in the future OracleClient and MySqlClient as well).
What I'm trying to do is create a unification of different enums - specifically - enums that describe sql parameters data types.
Currntly, I am supporting 4 enums -System.Data.DbType
,System.Data.SqlClient.SqlDbType
,System.Data.OleDb.OleDbType
,System.Data.Odbc.OdbcType
but if I want to add support for OracleClient or MySqlClient, I'll have to work pretty hard to add System.Data.OracleClient.OracleType
Or MySql.Data.MySqlClient.MySqlDbType
.
So I'm looking for a more elegant way of doing this then I came up with.
Here is my current code (It works great, but as I wrote, it's hard to add support for new enums):
So first, I have defined my own enum called ADONETType
. It has entries such as Boolean
, Byte
, Binary
, Char
etc'.
Then I've created a static class called DBTypeConverter
to provide extension methods to this enum, so that it's value could be converted to the other enums.
This is what this class looks like:
internal static class DBTypeConverter
{
#region private members
private static List<DbTypeMap> _Map;
#endregion private members
#region static constructor
static DBTypeConverter()
{
_Map = new List<DbTypeMap>()
{
new DbTypeMap(ADONETType.Boolean, DbType.Boolean, SqlDbType.Bit, OleDbType.Boolean, OdbcType.Bit),
new DbTypeMap(ADONETType.Byte, DbType.Byte, SqlDbType.TinyInt, OleDbType.UnsignedTinyInt , OdbcType.TinyInt),
new DbTypeMap(ADONETType.Binary, DbType.Binary, SqlDbType.Binary, OleDbType.Binary, OdbcType.Binary),
// ... more of the same here ...
new DbTypeMap(ADONETType.Xml, DbType.Xml, SqlDbType.Xml, null, null)
};
}
#endregion static constructor
#region methods
internal static DbType ToDbType(this ADONETType type)
{
return type.ConvertTo<DbType>();
}
internal static SqlDbType ToSqlType(this ADONETType type)
{
return type.ConvertTo<SqlDbType>();
}
internal static OleDbType ToOleDbType(this ADONETType type)
{
return type.ConvertTo<OleDbType>();
}
internal static OdbcType ToOdbcType(this ADONETType type)
{
return type.ConvertTo<OdbcType>();
}
private static dynamic ConvertTo<T>(this ADONETType type)
{
var returnValue = _Map.First(m => m.ADONETType == type).GetValueByType(typeof(T));
if(returnValue != null)
{
return returnValue;
}
throw new NotSupportedException(string.Format("ADONETType {0} is not supported for {1}", type, typeof(T)));
}
#endregion methods
#region private struct
private struct DbTypeMap
{
#region ctor
public DbTypeMap(ADONETType adonetType, DbType? dbType, SqlDbType? sqlDbType, OleDbType? oleDbType, OdbcType? odbcType)
: this()
{
ADONETType = adonetType;
DbType = dbType;
SqlDbType = sqlDbType;
OleDbType = oleDbType;
OdbcType = odbcType;
}
#endregion ctor
#region properties
internal ADONETType ADONETType { get; private set; }
internal DbType? DbType { get; private set; }
internal SqlDbType? SqlDbType { get; private set; }
internal OleDbType? OleDbType { get; private set; }
internal OdbcType? OdbcType { get; private set; }
#endregion properties
#region methods
internal dynamic GetValueByType(Type type)
{
if (type == typeof(ADONETType))
{
return this.ADONETType;
}
if(type == typeof(DbType))
{
return this.DbType;
}
if (type == typeof(SqlDbType))
{
return this.SqlDbType;
}
if (type == typeof(OleDbType))
{
return this.OleDbType;
}
if (type == typeof(OdbcType))
{
return this.OdbcType;
}
return null;
}
#endregion methods
}
#endregion private struct
}
Now, as you can see, In order to provide support for, say, OracleClient, I'll have to do the following:
OracleType
in the DbTypeMap
private struct.DbTypeMap
constructor to accept also the oracle type.GetValueByType
method.DBTypeConverter
.internal static OracleType ToOracleType(this ADONETType type)
) to DBTypeConverter
.Obvoisuly, this is a lot of work and I would much rather find another way to unify these enums, so adding support to new clients will be easier.
This is when your experties come in to play.
Assuming you really need this (consider Jeroen's comment, if not then you may reuse this for something else...) you can simplify things using a list of equivalences. It's simply a list of arrays where array items are equivalent. I'm using an array instead of a class because I do not need to add properties and ctor arguments when adding a new equivalence. To store the equivalence I'm using the special Enum
base class but also object
works pretty well (no need, AFAIK, for dynamic
).
Finding a conversion is then just matter of a search inside these lists (code here is more explicative than performance wise):
public static TTo ConvertTo<TTo>(Enum value)
{
var set = Mapping.FirstOrDefault(values => Array.IndexOf(values, value) != -1);
if (set == null)
throw new InvalidOperationException($"Value {value} is unknown");
return (TTo)(object)set.First(x => x.GetType() == typeof(TTo));
}
Populate Mapping
list as required, it may be defined, for example, as:
private static List<Enum[]> Mapping = new List<Enum[]>
{
new Enum[] { ADONETType.Byte, DbType.Byte, SqlDbType.TinyInt },
new Enum[] { ADONETType.Boolean, DbType.Boolean, SqlDbType.Bit },
new Enum[] { ADONETType.Binary, DbType.Binary, SqlDbType.Binary },
new Enum[] { ADONETType.Xml, DbType.Xml },
};
Note that ugly double cast (TTo)(object)
...ahhhh .NET Generics...a better solution is welcome. To support a new equivalence you still need to map all enum's values into this table, annoying but localized in one single place. If conversion isn't possible (value value
isn't defined anywhere) or there is not a known conversion to TTo
(for example the last DbType.Xml
) then InvalidOperationException
is thrown.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With