【问题标题】:Dapper and Enums as StringsDapper 和枚举作为字符串
【发布时间】:2016-05-16 23:13:49
【问题描述】:

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

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

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

如果我无法解决这个问题,我可能需要搬回 EF..

【问题讨论】:

  • 你能给我们举个例子吗?您需要在参数中为 Dapper 提供正确的值。另外,您能否详细说明在 varchar 字段中存储整数?这似乎不正确。
  • 我正在使用 Dapper Extensions - 所以我无法控制参数。此外 - varchar 字段中的整数由 Dapper 存储 - 我在数据库中找到“1” - 将在早上添加代码示例。
  • 核心 dapper 库当前将枚举作为其原始基础值传输(因此:如果您的枚举是 int 类型,则为 int,默认值)。我怀疑 RDBMS 很乐意将其转换为 varchar。所以目前的行为对我来说并不完全震惊。支持你正在做的事情听起来确实很可取(特别是因为 dapper 会很高兴地将字符串到枚举中),但与所有事情一样:它需要人类时间来考虑、设计、实现、测试、记录并支持这些变化。

标签: c# .net dapper


【解决方案1】:

有一种方法,我认为它更健壮和干净。

我提供的解决方案适用于任何枚举,但它涉及一些额外的编码。它还涉及在 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 };

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

【讨论】:

  • 我实际上也尝试过为普通枚举使用自定义处理程序,但这似乎不起作用。
  • 显然它是 Dapper 中一个长期存在的错误,即不为枚举调用 ITypeHandlers。 github.com/DapperLib/Dapper/issues/259
【解决方案2】:

感谢 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();
        }
    }
}

【讨论】:

    【解决方案3】:

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

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

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

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

    【讨论】:

    • 你能添加一个例子吗,这对我来说不是开箱即用的
    • 我收到此错误:System.ArgumentException:传入的值必须是枚举基础或枚举的基础类型,例如 Int32。 (参数“值”)
    • 它不起作用。
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      你可以传入一个字典,而不是传入你的数据对象,你可以传入一个由你的对象构建的字典,其中枚举转换为字典中的字符串(因此 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);
      }
      

      【讨论】:

        【解决方案6】:

        我发现这种方法适用于 DapperExtensions。在您的班级中按照正常情况设置一个 Enum 字段,然后为 Enum 设置一个第二个字符串字段,该字段表示您将保留的值。然后,您可以将映射设置为忽略 Enum 字段,而是保留字符串值。例如

        // 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");
        

        将第二个字符串枚举作为类的私有成员,但您必须将其公开才能使其正常工作 AFAIK

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-03-27
          • 2015-12-19
          • 2011-10-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多