Dapper 和枚举作为字符串
Dapper and Enums as Strings
我正在尝试使用 Dapper
和 Dapper-Extensions
并将我的 enums
在数据库上序列化为 string
。
现在它们被序列化为整数(在 VARCHAR
字段中)。
有什么办法吗?
我可以添加任何自定义类型映射吗?
如果我无法解决这个问题,我可能需要回到 EF..
感谢 Marc Gravell 的回复:
唯一的方法是手动插入。
同时使用以下 post:How do I perform an insert and return inserted identity with Dapper?
下面是我的解决方案。
请注意,选择会自动工作:您可以直接使用 Dapper(扩展)GetList<T>
,不需要映射到枚举返回。
public enum ComponentType
{
First,
Second,
Third
}
public class Info
{
public int Id { get; set; }
public ComponentType InfoComponentType { get; set; }
public static void SaveList(List<Info> infoList)
{
string ConnectionString = GetConnectionString();
using (SqlConnection conn = new SqlConnection(ConnectionString))
{
conn.Open();
foreach (Info info in infoList)
{
string sql = @"INSERT INTO [Info] ([InfoComponentType])
VALUES (@InfoComponentType);
SELECT CAST(SCOPE_IDENTITY() AS INT)";
int id = conn.Query<int>(sql, new
{
InfoComponentType = info.InfoComponentType.ToString()
}).Single();
info.Id = id;
}
conn.Close();
}
}
}
有一种方法,我认为它更健壮和干净。
我提供的解决方案适用于任何枚举,但它涉及一些额外的编码。它还涉及在 Dapper 中添加自定义类型处理程序。但是,如果这个答案获得一些选票,我将更改 Dapper 源代码以将此解决方案自动包含在类型处理中并请求拉取请求。
我实际实现了这个解决方案并在生产中使用它。
开始了。
首先是将用作枚举的结构(不是 class,因为该结构仅包含一个字符串引用):
public struct Country
{
string value;
public static Country BE => "BE";
public static Country NL => "NL";
public static Country DE => "DE";
public static Country GB => "GB";
private Country(string value)
{
this.value = value;
}
public static implicit operator Country(string value)
{
return new Country(value);
}
public static implicit operator string(Country country)
{
return country.value;
}
}
现在我们需要这个结构的类型处理程序
public class CountryHandler : SqlMapper.ITypeHandler
{
public object Parse(Type destinationType, object value)
{
if (destinationType == typeof(Country))
return (Country)((string)value);
else return null;
}
public void SetValue(IDbDataParameter parameter, object value)
{
parameter.DbType = DbType.String;
parameter.Value = (string)((dynamic)value);
}
}
在应用程序启动的某个地方,我们必须向 Dapper 注册类型处理程序
Dapper.SqlMapper.AddTypeHandler(typeof(Country), new CountryHandler());
现在您可以简单地将国家/地区用作 "enum"。例如:
public class Address
{
public string Street { get; set; }
public Country Country { get; set; }
}
var addr = new Address { Street = "Sesamestreet", Country = Country.GB };
缺点当然是枚举在内存中不是由整数而是字符串支持的。
您可以传入一个基于您的对象构建的字典,而不是传递您的数据对象,并将枚举转换为字典中的字符串(因此 Dapper 永远不会看到枚举)
iow 而不是说
connection.Query<MyDataObjectType>(sql, myDataObject);
你可以做到
connection.Query<MyDataObjectType>(sql, myDataObject.AsDapperParams());
然后有一个像
这样的方法
public static Dictionary<string, object> AsDapperParams(this object o)
{
var properties = o.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(c => c.CanRead).ToArray();
return properties
.Select(c => new {Key = c.Name, Value = c.GetValue(o), Type = c.PropertyType})
.ToDictionary(
c => c.Key,
c => (c.Type.IsEnum || Nullable.GetUnderlyingType(c.Type)
?.IsEnum == true) ? c.Value.ToString() : c.Value);
}
我的技术与 neeohw 的技术相似,但让我使用真正的枚举。而且它是通用的,所以我不必写很多次。
有一个包装枚举值的不可变结构。它有一个 属性 和隐式转换,以及一个通用的自定义类型处理程序。
public readonly struct DapperableEnum<TEnum> where TEnum : Enum
{
[JsonConverter(typeof(StringEnumConverter))]
public TEnum Value { get; }
static DapperableEnum()
{
Dapper.SqlMapper.AddTypeHandler(typeof(DapperableEnum<TEnum>), new DapperableEnumHandler<TEnum>());
}
public DapperableEnum(TEnum value)
{
Value = value;
}
public DapperableEnum(string description)
{
Value = EnumExtensions.GetValueByDescription<TEnum>(description);
}
public static implicit operator DapperableEnum<TEnum>(TEnum v) => new DapperableEnum<TEnum>(v);
public static implicit operator TEnum(DapperableEnum<TEnum> v) => v.Value;
public static implicit operator DapperableEnum<TEnum>(string s) => new DapperableEnum<TEnum>(s);
}
public class DapperableEnumHandler<TEnum> : SqlMapper.ITypeHandler
where TEnum : Enum
{
public object Parse(Type destinationType, object value)
{
if (destinationType == typeof(DapperableEnum<TEnum>))
{
return new DapperableEnum<TEnum>((string)value);
}
throw new InvalidCastException($"Can't parse string value {value} into enum type {typeof(TEnum).Name}");
}
public void SetValue(IDbDataParameter parameter, object value)
{
parameter.DbType = DbType.String;
parameter.Value =((DapperableEnum<TEnum>)value).Value.GetDescription();
}
}
我使用静态构造函数在启动时自动注册类型处理程序。
我使用 GetDescription / GetValueByDescription(与 this answer 相同的想法)来支持不是有效 C# 枚举值的字符串。如果您不需要此功能,ToString 和 Enum.Parse 也可以。
JsonConverter 属性使 Json.Net 也使用字符串值。当然,如果你不使用 Json.Net
,请将其删除
这是一个例子:
enum Holiday
{
Thanksgiving,
Christmas,
[Description("Martin Luther King, Jr.'s Birthday")]
MlkDay,
Other,
}
class HolidayScheduleItem : IStandardDaoEntity<HolidayScheduleItem>
{
public DapperableEnum<Holiday> Holiday {get; set;}
public DateTime When {get; set;}
}
并且调用代码可以使用普通的枚举值。
var item = new HolidayScheduleItem()
{
Holiday = Holiday.MlkDay,
When = new DateTime(2021, 1, 18)
};
它适用于普通 Dapper 或 Dapper.Contrib:
await conn.ExecuteAsync("INSERT HolidayScheduleItem ([Holiday], [When])
VALUES(@Holiday, @When)", item);
await conn.InsertAsync(item);
我无法获得 ITypeHandler
使用 enum
的建议。但是,我在分析 Dapper 生成的 SQL 时发现它正在将 enum
参数声明为 int
。所以我尝试为 enum
类型添加类型映射。
在应用程序启动时添加这个 Dapper 配置对我有用。
Dapper.SqlMapper.AddTypeMap(typeof(MyEnum), DbType.String);
然后我照常使用connection.Execute(updateSql, model)
。不需要使用 .ToString()
或任何其他显式转换。基础列是 varchar(20)
.
我发现这种方法适用于 DapperExtensions。按照正常情况在 class 中设置枚举字段,然后为枚举设置第二个字符串字段,代表您将保留的值。然后,您可以将映射设置为忽略枚举字段,而是保留字符串值。例如
// enum field
public Frequency Frequency { get; set;}
// string field of the same enum that you are going to persist
public string DbFrequency
{
get { return this.Frequency.ToString(); }
set { this.Frequency = Enum.Parse<Frequency>(value); }
}
// in your mappings do this
Map(f => f.Frequency).Ignore();
Map(f => f.DbFrequency).Column("Frequency");
将第二个字符串枚举作为 class 的私有成员,但您必须使其成为 public 才能工作 AFAIK
我正在尝试使用 Dapper
和 Dapper-Extensions
并将我的 enums
在数据库上序列化为 string
。
现在它们被序列化为整数(在 VARCHAR
字段中)。
有什么办法吗? 我可以添加任何自定义类型映射吗?
如果我无法解决这个问题,我可能需要回到 EF..
感谢 Marc Gravell 的回复:
唯一的方法是手动插入。
同时使用以下 post:How do I perform an insert and return inserted identity with Dapper?
下面是我的解决方案。
请注意,选择会自动工作:您可以直接使用 Dapper(扩展)GetList<T>
,不需要映射到枚举返回。
public enum ComponentType
{
First,
Second,
Third
}
public class Info
{
public int Id { get; set; }
public ComponentType InfoComponentType { get; set; }
public static void SaveList(List<Info> infoList)
{
string ConnectionString = GetConnectionString();
using (SqlConnection conn = new SqlConnection(ConnectionString))
{
conn.Open();
foreach (Info info in infoList)
{
string sql = @"INSERT INTO [Info] ([InfoComponentType])
VALUES (@InfoComponentType);
SELECT CAST(SCOPE_IDENTITY() AS INT)";
int id = conn.Query<int>(sql, new
{
InfoComponentType = info.InfoComponentType.ToString()
}).Single();
info.Id = id;
}
conn.Close();
}
}
}
有一种方法,我认为它更健壮和干净。
我提供的解决方案适用于任何枚举,但它涉及一些额外的编码。它还涉及在 Dapper 中添加自定义类型处理程序。但是,如果这个答案获得一些选票,我将更改 Dapper 源代码以将此解决方案自动包含在类型处理中并请求拉取请求。
我实际实现了这个解决方案并在生产中使用它。
开始了。
首先是将用作枚举的结构(不是 class,因为该结构仅包含一个字符串引用):
public struct Country
{
string value;
public static Country BE => "BE";
public static Country NL => "NL";
public static Country DE => "DE";
public static Country GB => "GB";
private Country(string value)
{
this.value = value;
}
public static implicit operator Country(string value)
{
return new Country(value);
}
public static implicit operator string(Country country)
{
return country.value;
}
}
现在我们需要这个结构的类型处理程序
public class CountryHandler : SqlMapper.ITypeHandler
{
public object Parse(Type destinationType, object value)
{
if (destinationType == typeof(Country))
return (Country)((string)value);
else return null;
}
public void SetValue(IDbDataParameter parameter, object value)
{
parameter.DbType = DbType.String;
parameter.Value = (string)((dynamic)value);
}
}
在应用程序启动的某个地方,我们必须向 Dapper 注册类型处理程序
Dapper.SqlMapper.AddTypeHandler(typeof(Country), new CountryHandler());
现在您可以简单地将国家/地区用作 "enum"。例如:
public class Address
{
public string Street { get; set; }
public Country Country { get; set; }
}
var addr = new Address { Street = "Sesamestreet", Country = Country.GB };
缺点当然是枚举在内存中不是由整数而是字符串支持的。
您可以传入一个基于您的对象构建的字典,而不是传递您的数据对象,并将枚举转换为字典中的字符串(因此 Dapper 永远不会看到枚举)
iow 而不是说
connection.Query<MyDataObjectType>(sql, myDataObject);
你可以做到
connection.Query<MyDataObjectType>(sql, myDataObject.AsDapperParams());
然后有一个像
这样的方法public static Dictionary<string, object> AsDapperParams(this object o)
{
var properties = o.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(c => c.CanRead).ToArray();
return properties
.Select(c => new {Key = c.Name, Value = c.GetValue(o), Type = c.PropertyType})
.ToDictionary(
c => c.Key,
c => (c.Type.IsEnum || Nullable.GetUnderlyingType(c.Type)
?.IsEnum == true) ? c.Value.ToString() : c.Value);
}
我的技术与 neeohw 的技术相似,但让我使用真正的枚举。而且它是通用的,所以我不必写很多次。
有一个包装枚举值的不可变结构。它有一个 属性 和隐式转换,以及一个通用的自定义类型处理程序。
public readonly struct DapperableEnum<TEnum> where TEnum : Enum
{
[JsonConverter(typeof(StringEnumConverter))]
public TEnum Value { get; }
static DapperableEnum()
{
Dapper.SqlMapper.AddTypeHandler(typeof(DapperableEnum<TEnum>), new DapperableEnumHandler<TEnum>());
}
public DapperableEnum(TEnum value)
{
Value = value;
}
public DapperableEnum(string description)
{
Value = EnumExtensions.GetValueByDescription<TEnum>(description);
}
public static implicit operator DapperableEnum<TEnum>(TEnum v) => new DapperableEnum<TEnum>(v);
public static implicit operator TEnum(DapperableEnum<TEnum> v) => v.Value;
public static implicit operator DapperableEnum<TEnum>(string s) => new DapperableEnum<TEnum>(s);
}
public class DapperableEnumHandler<TEnum> : SqlMapper.ITypeHandler
where TEnum : Enum
{
public object Parse(Type destinationType, object value)
{
if (destinationType == typeof(DapperableEnum<TEnum>))
{
return new DapperableEnum<TEnum>((string)value);
}
throw new InvalidCastException($"Can't parse string value {value} into enum type {typeof(TEnum).Name}");
}
public void SetValue(IDbDataParameter parameter, object value)
{
parameter.DbType = DbType.String;
parameter.Value =((DapperableEnum<TEnum>)value).Value.GetDescription();
}
}
我使用静态构造函数在启动时自动注册类型处理程序。
我使用 GetDescription / GetValueByDescription(与 this answer 相同的想法)来支持不是有效 C# 枚举值的字符串。如果您不需要此功能,ToString 和 Enum.Parse 也可以。
JsonConverter 属性使 Json.Net 也使用字符串值。当然,如果你不使用 Json.Net
,请将其删除这是一个例子:
enum Holiday
{
Thanksgiving,
Christmas,
[Description("Martin Luther King, Jr.'s Birthday")]
MlkDay,
Other,
}
class HolidayScheduleItem : IStandardDaoEntity<HolidayScheduleItem>
{
public DapperableEnum<Holiday> Holiday {get; set;}
public DateTime When {get; set;}
}
并且调用代码可以使用普通的枚举值。
var item = new HolidayScheduleItem()
{
Holiday = Holiday.MlkDay,
When = new DateTime(2021, 1, 18)
};
它适用于普通 Dapper 或 Dapper.Contrib:
await conn.ExecuteAsync("INSERT HolidayScheduleItem ([Holiday], [When])
VALUES(@Holiday, @When)", item);
await conn.InsertAsync(item);
我无法获得 ITypeHandler
使用 enum
的建议。但是,我在分析 Dapper 生成的 SQL 时发现它正在将 enum
参数声明为 int
。所以我尝试为 enum
类型添加类型映射。
在应用程序启动时添加这个 Dapper 配置对我有用。
Dapper.SqlMapper.AddTypeMap(typeof(MyEnum), DbType.String);
然后我照常使用connection.Execute(updateSql, model)
。不需要使用 .ToString()
或任何其他显式转换。基础列是 varchar(20)
.
我发现这种方法适用于 DapperExtensions。按照正常情况在 class 中设置枚举字段,然后为枚举设置第二个字符串字段,代表您将保留的值。然后,您可以将映射设置为忽略枚举字段,而是保留字符串值。例如
// enum field
public Frequency Frequency { get; set;}
// string field of the same enum that you are going to persist
public string DbFrequency
{
get { return this.Frequency.ToString(); }
set { this.Frequency = Enum.Parse<Frequency>(value); }
}
// in your mappings do this
Map(f => f.Frequency).Ignore();
Map(f => f.DbFrequency).Column("Frequency");
将第二个字符串枚举作为 class 的私有成员,但您必须使其成为 public 才能工作 AFAIK