59

This is my Model Class where we have a Type, which could be a Zombie or Human.

public class User
{
    public int ID { get; set; }
    public string Name { get; set; }
    public Type Type { get; set; }
    public List<Weapon> WeaponsInList { get; set; }
}  

public enum Type
{   
    [Description("Zombie")]
    Zombie,
    
    [Description("Human")]
    Human
}

Currently, it is saving data in Int.

enter image description here

I want to save the data as Human and Zombie, not with int.

Grigory Zhadko
  • 1,484
  • 1
  • 19
  • 33
ZCoder
  • 2,155
  • 5
  • 25
  • 62
  • 1
    An enumeration is a static object, there's no point in saving it to the database... Really you should define the items in the database and then generate your enum in code using a T4 template. – The Muffin Man Sep 12 '15 at 18:49
  • 4
    An enum value _is_ an int. You should really save it as so. Unless you have a _very good_ reason to have it as a string in the DB.. Which I suppose you don't. – Pierre-Luc Pineault Sep 12 '15 at 19:11
  • 14
    @Pierre-LucPineault : is it not dangerous to store it as an int? Wouldn't all someone have to do is re-order the enum and then immediately all the values in your database is pointing to the wrong enum without any warning – Diskdrive Jun 23 '16 at 06:13
  • 1
    @Diskdrive You can assign a specific integer to your enum so even when reordered it doesn't change (And often with powers of 2 so you can declare it as a 'Flag'). But usually you just don't go reordering enums for fun. – Pierre-Luc Pineault Jun 23 '16 at 13:26
  • 33
    @Pierre-LucPineault, what Diskdrive is referring to is a real problem. Enums can get arranged for reasons other than fun, both validly and accidentally. It's a more robust solution to store the strings; those don't change. But if they do, no data is lost, just a db update is needed. Plus, looking at the table that way is more productive, and there's no need to number the values, which is nice on non-flag enums. In fact, this is how xml serialization works. So, at least some MS developers agree with Diskdrive. – toddmo Feb 25 '17 at 00:42

8 Answers8

65

In Entity Framework Core you can specify the built-in conversion.

If you have an enum type

public enum MyEnumType
{
    ...
}

and a model class with this property

public class EntityWithEnum
{
    public MyEnumType MyEnum { get; set; }
    ...
}

then you can add the built-in conversion

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<EntityWithEnum>()
        .Property(d => d.MyEnum)
        .HasConversion(new EnumToStringConverter<MyEnumType>());
}

More details here.

Martin Staufcik
  • 8,295
  • 4
  • 44
  • 63
  • 12
    This is only available in EF core. – Seth Jun 10 '19 at 17:27
  • 1
    This solution is very elegant and works perfectly. Thanks! – Rui Castro Oct 10 '19 at 10:22
  • 1
    I have a .NET Core 3.1 and I get this build error when using HasConversion Error CS1061 'PrimitivePropertyConfiguration' does not contain a definition for 'HasConversion' and no accessible extension method 'HasConversion' accepting a first argument of type 'PrimitivePropertyConfiguration' could be found (are you missing a using directive or an assembly reference?) – Ninos Sep 28 '20 at 05:09
  • 5
    use `.HasConversion();` – Cas Bloem Oct 06 '20 at 12:43
  • Where does `DataSetSemanticType` come from? it doesn't seem to be a known type in .NET and it's neither defined in the question. Please provide an example, I don't understand how this class should look like – Jérôme MEVEL Jan 22 '23 at 09:42
  • 1
    @JérômeMEVEL The answer has been modified to be more understandable. – Martin Staufcik Jan 22 '23 at 11:10
  • Ah ok I thought that was a custom mapping class for example to use the `Description` annotation. Thanks – Jérôme MEVEL Jan 22 '23 at 11:43
39

You can save the enum to the db as a string, and I agree with dotctor that it is not the best idea, but if you need to, you need to make a few changes.

public class User
{
    public int ID { get; set; }
    public string Name { get; set; }
    public List<Wepon> WeposInList { get; set; }

    [Column("Type")]
    public string TypeString
    {
       get { return Type.ToString(); }
       private set { Type= value.ParseEnum<Type>(); }
    }

    [NotMapped]
    public Type Type { get; set; }
}  

Add this extension class to your project.

public static class StringExtensions
{
    public static T ParseEnum<T>(this string value)
    {
        return (T)Enum.Parse(typeof(T), value, true);
    }
}

Full details are here - http://NoDogmaBlog.bryanhogan.net/2014/11/saving-enums-as-strings-with-entity-framework/

Bryan
  • 5,065
  • 10
  • 51
  • 68
  • 1
    how would I use this to track both the label and the id in the database? I'm getting a null argument exception when I fetch a record. – TWilly Sep 14 '16 at 22:34
  • 1
    Try removing [NotMapped] and set it to some other column in the db – Bryan Sep 15 '16 at 15:27
  • 4
    the only actual answer to the OP's question. :) – toddmo Feb 25 '17 at 00:43
  • Wouldn't this violate SOLID? By doing so your entity would not only describe your data, it would describe business as well. What I do is to have an enum, which I would seed to the database, and then refer it from there. – nkalfov Jun 27 '18 at 12:04
14

Furthering to Martin's answer, Entity Framework Core has a pre-defined converter for enum to string.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder
        .Entity<User>()
        .Property(e => e.Type)
        .HasConversion<string>();
}

The same can be achieved by:

public class User
{
    public int ID { get; set; }

    public string Name { get; set; }

    [Column(TypeName = "nvarchar(24)")]
    public Type Type { get; set; }

    public List<Weapon> WeaponsInList { get; set; }
}
j-petty
  • 2,668
  • 1
  • 11
  • 20
10

I had this problem as far as I remember and honestly I don't know why didn't MS add this feature (NH can do it like since always..).

Any ways, what I usually did is use const strings classes like:

public static class MyEnum
{
    public const string Foo = "Foo";
    public const string Bar = "Bar";
}

public class Client
{

    public string MyVal { get; set; }

    public Client()
    {
        MyVal = MyEnum.Bar;
    }

}

Cons - as simple as can be.

Downsides - you loose type checking (though it could be enforced programmatically).


So this time I tried to think of something more ambitious. So I took the concept described by Brian (which has some downsides when e.g. a given enum is used widely across the domain). And well.. I got the following working:

A base component class to store the values:

[ComplexType]
public class DbEnum<TEnum>
{
    public string _ { get; set; }

    public DbEnum()
    {
        _ = default(TEnum).ToString();
    }

    protected DbEnum(TEnum value)
    {
        _ = value.ToString();
    }

    public TEnum ToEnum()
    {
        return _.ToEnum<TEnum>();
    }

    public static implicit operator DbEnum<TEnum>(TEnum value)
    {
        return new DbEnum<TEnum>(value);
    }

    public static implicit operator TEnum(DbEnum<TEnum> value)
    {
        return value.ToEnum();
    }
}

... which would be basically sufficient.. except EF doesn't support generic types...

This means for every enum you have to have something like...

public enum PrivacyLevel
{
    Public,
    Friends,
    Private
}

public class PrivacyLevelEnum : DbEnum<PrivacyLevel>
{
    public PrivacyLevelEnum() : this(default (PrivacyLevel))
    {      
    }

    public PrivacyLevelEnum(PrivacyLevel value) : base(value)
    {
    }

    public static implicit operator PrivacyLevelEnum(PrivacyLevel value)
    {
        return new PrivacyLevelEnum(value);
    }

    public static implicit operator PrivacyLevel(PrivacyLevelEnum value)
    {
        return value.ToEnum();
    }
}

Which gives you some boiler-plate that could be easily generated e.g. using T4 templates.

Which finally ends you up with using:

public class CalendarEntry : Entity
{

    public virtual PrivacyLevelEnum PrivacyLevel { get; set; } = new PrivacyLevelEnum();

}

But since you have implicit conversion in place, class declarations are the only ones to be aware of the helper types.

Pawel Gorczynski
  • 1,227
  • 1
  • 15
  • 17
8

Explicit enum to string conversions would make your code messy and you'd have to keep parsing values. The same goes for lookup tables. Just add [Column] attribute to your enum field and specify TypeName as nvarchar (for SQL) or varchar (for postgres). Worked for me like a charm. In your case, for example :

public class User
{
    public int ID { get; set; }

    public string Name { get; set; }

    [Column(TypeName = "nvarchar(20)")]
    public Type Type { get; set; }

    public List<Wepon> WeposInList { get; set; }

    }  

You can read more about it in the official documentation here

Shivam Negi
  • 373
  • 5
  • 9
  • Owrked for me in EF Core. Nice. Especially note that it's downward compatible, i.e. after converting the db from int to string column type, the "integer strings" will still be parsed correctly. – Marc Oct 07 '20 at 13:32
  • This did not work without specifying an int<=>string converter in my context's `OnModelCreating` method. – Grant Birchmeier Nov 24 '20 at 23:07
3

It's not a good idea to store them as string but you can create look up tables for your enums with ef enum to lookup and it is very easy to use.

Hamid Pourjam
  • 20,441
  • 9
  • 58
  • 74
  • 16
    Can you please explain a bit why it is not a good idea to store enum as `nvarchar()`? Isn't text more readable than an integer in the database? – Blaise Feb 01 '18 at 20:49
  • 1
    For example easier to keep enum even for sorting. Otherwise you need to create some maps, where in enum just change the number. Also depends on db, where on some, string performance is worst than int. – dawid debinski Dec 19 '22 at 11:03
1

I thing, that it is much more useful to store them as int because you can then cast the int from DB very easily to the enum.

But if it what you desire, there are two approaches. You can save Type.Zombie.ToString() (or Type.Human.ToString() respectively) to database (which will be "Zombie"), or you can obtain the value of DescriptionAttribute, which you are using and save that to the DB. How to get the description is described here. - It will be also "Zombie" in this case, but it may be whatever else you write in the Description().

If you use ToString you can then use Enum.Parse to get the instance of the enum back. If you use the description, it is not that easy.

Community
  • 1
  • 1
Matyas
  • 1,122
  • 5
  • 23
  • 29
0

You can use Newtonsoft property

[JsonConverter(typeof(StringEnumConverter))]
public enum MyEnum
{
}
Truc
  • 386
  • 4
  • 12