Last active
April 24, 2023 17:22
-
-
Save JerryNixon/7a8ea867ef71dd8717d502a0d6ae951c to your computer and use it in GitHub Desktop.
Convert SQL Type to .NET Type
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Microsoft.SqlServer.TransactSql.ScriptDom; | |
public static class SqlUtilities | |
{ | |
public static string GetDotnetType(this SqlDataTypeOption sqlDataType, bool isNullable = false) | |
{ | |
if (IsUnsupportedType()) | |
{ | |
return string.Empty; | |
} | |
var dotnetType = typeof(string); | |
switch (sqlDataType) | |
{ | |
case SqlDataTypeOption.BigInt: dotnetType = typeof(long); break; | |
case SqlDataTypeOption.Binary: | |
case SqlDataTypeOption.Image: | |
case SqlDataTypeOption.VarBinary: dotnetType = typeof(byte[]); break; | |
case SqlDataTypeOption.Bit: dotnetType = typeof(bool); break; | |
case SqlDataTypeOption.Char: dotnetType = typeof(char); break; | |
case SqlDataTypeOption.Time: dotnetType = typeof(TimeOnly); break; | |
case SqlDataTypeOption.Date: dotnetType = typeof(DateOnly); break; | |
case SqlDataTypeOption.DateTime: | |
case SqlDataTypeOption.SmallDateTime: dotnetType = typeof(DateTime); break; | |
case SqlDataTypeOption.DateTime2: | |
case SqlDataTypeOption.DateTimeOffset: dotnetType = typeof(DateTimeOffset); break; | |
case SqlDataTypeOption.Decimal: | |
case SqlDataTypeOption.Money: | |
case SqlDataTypeOption.Numeric: dotnetType = typeof(decimal); break; | |
case SqlDataTypeOption.Float: dotnetType = typeof(double); break; | |
case SqlDataTypeOption.Int: dotnetType = typeof(int); break; | |
case SqlDataTypeOption.NChar: | |
case SqlDataTypeOption.NVarChar: | |
case SqlDataTypeOption.Text: | |
case SqlDataTypeOption.VarChar: dotnetType = typeof(string); break; | |
case SqlDataTypeOption.Real: dotnetType = typeof(float); break; | |
case SqlDataTypeOption.SmallInt: dotnetType = typeof(short); break; | |
case SqlDataTypeOption.TinyInt: dotnetType = typeof(byte); break; | |
case SqlDataTypeOption.UniqueIdentifier: dotnetType = typeof(Guid); break; | |
} | |
return dotnetType.Name + (isNullable ? "?" : string.Empty); | |
bool IsUnsupportedType() | |
{ | |
var types = new[] | |
{ | |
SqlDataTypeOption.Sql_Variant, | |
SqlDataTypeOption.Timestamp, | |
SqlDataTypeOption.Rowversion, | |
}; | |
return types.Contains(sqlDataType); | |
} | |
} | |
public static string GetDotnetType(this string sqlDataType, bool isNullable = false) | |
{ | |
RemoveAnyPercision(); | |
if (!Enum.TryParse(sqlDataType, true, out SqlDataTypeOption dataTypeOption)) | |
{ | |
return string.Empty; | |
} | |
return dataTypeOption.GetDotnetType(isNullable); | |
void RemoveAnyPercision() | |
{ | |
int index = sqlDataType.IndexOf("("); | |
if (index != -1) | |
{ | |
sqlDataType = sqlDataType.Substring(0, index).Trim(); | |
} | |
} | |
} | |
} |
- I'd return
null
instead of an empty string when the type is unsupported. - In
RemoveAnyRecision
, useint index = sqlDataType.IndexOf('(', StringComparison.Ordinal);
instead. This won't allocate a string and use binary comparison when searching. - I'd implement
IsUnsupportedType
like this:
bool IsUnsupportedType()
{
return sqlDataType is SqlDataTypeOption.Sql_Variant
or SqlDataTypeOption.Timestamp
or SqlDataTypeOption.Rowversion;
}
This won't allocate and will be much faster.
OK so I mentioned on Twitter that I would take a pass at it. Here is a summary of the changes I made:
- I renamed the class to me more explicitt about its purpose.
- I've simplified most functions to expression-bodied functions, as they don't have a terribly complex codepath.
- The primary method has been changed to a switch expression and dramatically simplified to:
- Eliminate the need to check separately for unsupported types
- Eliminate the need for the
dotnetType
variable and any subsequent multi-allocations and/or assignments - Eliminate switch case failovers, which can lead to unintended results
- Internal functions have been eliminated, as they are more difficult to unit test & achieve proper code coverage.
- I've renamed
GetDotNetType
toGetDotNetTypeString
, since the function does not return 'Type`. - I've modified
RemoveAnyPercision
by:- renaming it to
RemoveSqlPrecision
to be more explicit about what it does - incorporated @CollinAlpert's feedback to reduce allocations and speed up the results
- simplified the return into a ternary expression
- renaming it to
- I've added a function to format the .NET type to a string and optimized it for speed and memory allocations.
- I've moved simple overloads to the top of the file, so they "fall through" down to the "base" method".
- I've changed the string extension for
GetDotNetTypeString
to reduce allocations and simplified the return to a ternary expression. - I've separated the public functions from the private ones and put each section in alphabetical order to make it easier to navigate through the code file.
- I've fully-documented the file so that any other developers using the code understand what it does and how it works.
Here is the code.
using Microsoft.SqlServer.TransactSql.ScriptDom;
using System;
/// <summary>
/// A set of Extension Methods that primarily deal with <see cref="SqlDataTypeOption"/>.
/// </summary>
public static class SqlDataTypeOptionExtensions
{
#region Public Methods
/// <summary>
/// Returns a string describing the .NET type mapping for a given <see cref="SqlDataTypeOption"/>.
/// </summary>
/// <param name="sqlDataType">A string representing the data type from the SQL source.</param>
/// <param name="isNullable">Specifies whether or not the result should be nullable.</param>
/// <returns>
/// A <see cref="string"/> representing the .NET type corresponding to the given <see cref="SqlDataTypeOption"/>,
/// with any appropriate nullability annotations.
/// </returns>
public static string GetDotNetTypeString(this string sqlDataType, bool isNullable = false) =>
// RWM: If we can parse the SqlType string with the precision removed, return the .NET type, otherwise return nothing.
Enum.TryParse(RemoveSqlPrecision(sqlDataType), true, out SqlDataTypeOption dataTypeOption)
? GetDotNetTypeString(dataTypeOption, isNullable)
: string.Empty;
/// <summary>
/// Returns a string describing the .NET type mapping for a given <see cref="SqlDataTypeOption"/>.
/// </summary>
/// <param name="sqlDataType">A <see cref="SqlDataTypeOption"/> representing the data type from the SQL source.</param>
/// <param name="isNullable">Specifies whether or not the result should be nullable.</param>
/// <returns>
/// A <see cref="string"/> representing the .NET type corresponding to the given <see cref="SqlDataTypeOption"/>,
/// with any appropriate nullability annotations.
/// </returns>
public static string GetDotNetTypeString(this SqlDataTypeOption sqlDataType, bool isNullable = false) =>
sqlDataType switch
{
// RWM: This list REALLY should be sorted in alphabetical order by the SqlDataTypeOption enum.
SqlDataTypeOption.BigInt => FormatDotNetTypeString(typeof(long), isNullable),
SqlDataTypeOption.Binary => FormatDotNetTypeString(typeof(byte[]), isNullable),
SqlDataTypeOption.Image => FormatDotNetTypeString(typeof(byte[]), isNullable),
SqlDataTypeOption.VarBinary => FormatDotNetTypeString(typeof(byte[]), isNullable),
SqlDataTypeOption.Bit => FormatDotNetTypeString(typeof(bool), isNullable),
SqlDataTypeOption.Char => FormatDotNetTypeString(typeof(char), isNullable),
SqlDataTypeOption.Time => FormatDotNetTypeString(typeof(TimeOnly), isNullable),
SqlDataTypeOption.Date => FormatDotNetTypeString(typeof(DateOnly), isNullable),
SqlDataTypeOption.DateTime => FormatDotNetTypeString(typeof(DateTime), isNullable),
SqlDataTypeOption.SmallDateTime => FormatDotNetTypeString(typeof(DateTime), isNullable),
SqlDataTypeOption.DateTime2 => FormatDotNetTypeString(typeof(DateTimeOffset), isNullable),
SqlDataTypeOption.DateTimeOffset => FormatDotNetTypeString(typeof(DateTimeOffset), isNullable),
SqlDataTypeOption.Decimal => FormatDotNetTypeString(typeof(decimal), isNullable),
SqlDataTypeOption.Money => FormatDotNetTypeString(typeof(decimal), isNullable),
SqlDataTypeOption.Numeric => FormatDotNetTypeString(typeof(decimal), isNullable),
SqlDataTypeOption.Float => FormatDotNetTypeString(typeof(double), isNullable),
SqlDataTypeOption.Int => FormatDotNetTypeString(typeof(int), isNullable),
SqlDataTypeOption.NChar => FormatDotNetTypeString(typeof(string), isNullable),
SqlDataTypeOption.NVarChar => FormatDotNetTypeString(typeof(string), isNullable),
SqlDataTypeOption.Text => FormatDotNetTypeString(typeof(string), isNullable),
SqlDataTypeOption.VarChar => FormatDotNetTypeString(typeof(string), isNullable),
SqlDataTypeOption.Real => FormatDotNetTypeString(typeof(float), isNullable),
SqlDataTypeOption.SmallInt => FormatDotNetTypeString(typeof(short), isNullable),
SqlDataTypeOption.TinyInt => FormatDotNetTypeString(typeof(byte), isNullable),
SqlDataTypeOption.UniqueIdentifier => FormatDotNetTypeString(typeof(Guid), isNullable),
// RWM: If it's not explicitly mentioned, it's unsupported.
_ => string.Empty
};
#endregion
#region Private Methods
/// <summary>
/// Returns a string representation of a .NET type, including nullability annotations.
/// </summary>
/// <param name="type">The <see cref="Type"/> to format.</param>
/// <param name="isNullable">Specifies whether or not the result should be nullable.</param>
/// <returns>A string containing the simple name of the .NET type, followed by a "?" if the type is nullable.</returns>
/// <remarks>
/// Explicit interpolation chosen because it is the fastest.
/// See https://stackoverflow.com/questions/32342392/string-interpolation-vs-string-format
/// </remarks>
private static string FormatDotNetTypeString(Type type, bool isNullable = false) =>
$"{type.Name}{(isNullable ? "?" : string.Empty)}";
/// <summary>
/// Removes precision information from the sql type string.
/// </summary>
/// /// <param name="sqlDataType">A <see cref="string"/> containging the SQL data type for the column.</param>
/// <returns>A string representing the SQL data type, but without precision details.</returns>
private static string RemoveSqlPrecision(string sqlDataType)
{
int index = sqlDataType.IndexOf("(", StringComparison.Ordinal);
return index >= 0 ? sqlDataType.Substring(0, index).Trim() : sqlDataType;
}
#endregion
}
I hope that helps!
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
RemoveAnyPercision()? - Precision?