Dapper和Enums作为字符串

时间:2016-05-16 23:13:49

标签: c# .net dapper dapper-extensions

我正在尝试使用DapperDapper-Extensions,并将数据库中的enums序列化为string

现在它们被序列化为整数(在VARCHAR字段内)而不是。

有没有办法做到这一点? 我可以添加任何自定义类型映射吗?

如果我无法通过,我可能需要回到EF。

5 个答案:

答案 0 :(得分:4)

有一种方法,我认为它更健壮和简洁。

我提供的解决方案适用于任何枚举,但是涉及一些额外的编码。它还涉及在Dapper中添加自定义类型处理程序。但是,如果这个答案获得了投票,我将更改Dapper源代码,以在类型处理中自动包含此解决方案,并请求拉取请求。

我实际上实现了此解决方案,并将其用于生产中。

在这里。

首先将用作枚举的结构(而不​​是类,因为该结构仅包含一个字符串引用):

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());

现在,您可以简单地将Country用作“枚举”。例如:

public class Address
{
     public string Street { get; set; }
     public Country Country { get; set; }
}

var addr = new Address { Street = "Sesamestreet", Country = Country.GB };

当然,缺点是枚举不是在内存中由整数而是由字符串支持。

答案 1 :(得分:1)

感谢Marc Gravell回复:

唯一的方法是手动进行插入。

还使用以下帖子:How do I perform an insert and return inserted identity with Dapper?

在我的解决方案之下。

请注意,选择自动工作:您可以直接使用Dapper(Extensions)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();
        }
    }
}

答案 2 :(得分:1)

除了传入数据对象外,您还可以传入由对象构建的字典,其中将枚举转换为字典中的字符串(因此Dapper从不看到枚举)

现在不用说了

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);
}

答案 3 :(得分:0)

我的技术与 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);

From my DB:

答案 4 :(得分:0)

我无法获得使用 ITypeHandlerenum 建议。但是,我在分析 Dapper 生成的 SQL 时注意到它将 enum 参数声明为 int。所以我尝试为 enum 类型添加类型映射。

在应用程序启动时添加这个 Dapper 配置对我有用。

Dapper.SqlMapper.AddTypeMap(typeof(MyEnum), DbType.String);

然后我像往常一样使用 connection.Execute(updateSql, model)。不需要使用 .ToString() 或任何其他显式转换。基础列是 varchar(20)