Open greghroberts opened 7 years ago
Hi @greghroberts, We don't have any plan for supporting enum column type yet. It seems that the official sqlserver provider for ef core doesn't support this too. So change this issue into backlog.
Enum column types are specific to MySQL. I don't believe SqlServer has an equivalent yet. This feature should be pretty easy to do as it just needs awareness of the custom type similarly to what u are doing with JSON type.
For reference it seems the npgsql is working through some of this too. https://github.com/npgsql/Npgsql.EntityFrameworkCore.PostgreSQL/issues/27 I believe Postgres is even more complex since enums are defined as new types instead of a general enum type.
Supporting ENUM
type is still on the backlog for the ADO.NET driver we are switching to in 1.0.1.
MySqlConnector now has support for ENUM
columns in 0.7.3.
MySQL Server stores ENUM
columns as integers but sends them to/from clients as strings. So MySqlConnector will deserialize them as strings and MySqlDataReader.GetValue(i)
will return a string
.
If you want to convert them "automagically" to System.Enum
values, you will need to detect when GetDataTypeName
returns "ENUM"
, know the type of .NET enum
you wish to convert to, and call Enum.Parse(YourEnumType, reader.GetString(n), ignoreCase: true)
. The MySqlConnector library will not do this automatically.
@Kagamine I will take a look at this one, we need it for our project.
It appears that value conversions are being introduced in EF Core 2.1 which would allow storing enums as strings.
Starting with EF Core 2.1, value conversions can be applied to transform the values obtained from columns before they are applied to properties, and vice versa.
https://blogs.msdn.microsoft.com/dotnet/2018/02/02/entity-framework-core-2-1-roadmap/
Hi, we where hoping to use this feature with ef core and Pomelo.EntityFrameworkCore.MySql. Is this working? or planned?
I will implement this as our first new feature for 5.0.0
.
Oh... have to wait for 7.0 :cry:
oh my god, maybe 8.0?
By default, EF Core maps CLR enum
type values to CLR Int32
before they get stored in the database table.
So without any Fluent API setup, using a CLR enum
works but does not map to a store type of ENUM
but of INT
.
However, you can simply setup an entity property to use a CLR enum
type and a ENUM
database store type.
Take a look at the following sample console app, that demonstrates this:
It generates the following CREATE TABLE
statement, that includes the ENUM
column type:
As mentioned in the code, unless you let your CLR enum
start with 1
, it will start with 0
. However, MySQL ENUM
values always start with 1
.
That is technically not an issue, because MySqlConnector and the converter translates this fine here, since the converter translation happens using the name of the CLR enum
instead of the value.
But if you care about using the same underlying integer value for CLR that is used by MySQL, then you want to let your CLR enum
explicitly start from 1
:
Of course you could alter the default conversion logic in any way you want, e.g. if your database ENUM
values cannot be represented by CLR enum
names (like chocolate-dipped
), you could also (or additionally) check a custom attribute on the CLR enum
values and translate the CLR enum
value to that attribute value before sending it to the database.
@lauxjpn With MySQL version 5.7 it generates the migrations with a defaultValue='' for the mandatory enums. And there is no way to make it non-default.
Migration:
migrationBuilder.AddColumn<string>(
name: "Criticality",
table: "LubricationPlans",
type: "enum('Critical', 'SemiCritical', 'NonCritical')",
nullable: false,
defaultValue: "")
.Annotation("MySql:CharSet", "utf8mb4");
Config:
var criticalityValues = Enum.GetValues(typeof(LubricationPlanCriticality))
.Cast<LubricationPlanCriticality>()
.Select(x => "'" + x + "'")
.ToList();
builder
.Property(lubricationPlan => lubricationPlan.Criticality)
.HasColumnType("ENUM(" + string.Join(",", criticalityValues) + ")")
.IsRequired()
.HasConversion(
v => v.ToString(),
v =>
(LubricationPlanCriticality)
Enum.Parse(typeof(LubricationPlanCriticality), v)
);
If anyone want to use a convenient option to address this issue, here is a code snippet for that.
public static class EnumUtils
{
public static string ToMySqlEnum<TEnum>()
where TEnum : Enum
{
var enumNames = Enum
.GetNames(typeof(TEnum))
.Select(enumValue => $"'{enumValue}'")
.ToArray();
var enumString = string.Join(", ", enumNames);
return $"enum({enumString})";
}
}
In DbContext class:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
var statusEnum = entityType.FindProperty("Status");
statusEnum?.SetColumnType(EnumUtils.ToMySqlEnum<Status>());
}
}
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
configurationBuilder.Properties<Enum>().HaveConversion<string>();
}
In application with separate configuration class:
builder.Property(x => x.Status)
.HasColumnType(EnumUtils.ToMySqlEnum<Status>())
.HasConversion<string>();
The generated SQL in the migraton:
status = table.Column<string>(type: "enum('Published', 'Pending', 'Draft')", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
@BartoGabriel If a property is mandatory (which an non-nullable CLR enum
would be) you would need to specify a default value for that property in the model (the default value for a non-nullable string is an empty string), e.g. .HasDefaultValue("NonCritical")
.
You could also make your property nullable, which would result in a default value of null
implicitly.
@lauxjpn MySQL allows the creation of an enum type column that is mandatory and without specifying a default value. At the database level, I usually create it like this (it forces the user to specify it in the inserts):
ALTER TABLE `test`
ADD COLUMN `enumColumn` ENUM('Y','N') NOT NULL;
I believe that's why I expected the migration not to specify that default value, which also doesn't make sense since the string '' does not comply with any enum value.
I understand about specifying HasDefaultValue... And I think the developer should consider, when using enums in this way, setting the HasDefaultValue equal to the default value that C# assigns to a non-nullable enum (I'm not sure if I explained myself correctly...).
Pomelo doesn't have special support for MySQL enum
values. They are currently just handled as any other CLR string
. And a non-nullable string
has a default value of ""
(empty string). It has no concept of what an enum
column type is. So if you want to use a CLR string
as the representation of a MySQL enum
, you need to specify the default value manually.
ALTER TABLE `test` ADD COLUMN `enumColumn` ENUM('Y','N') NOT NULL;
MySQL stores enum
columns internally as integer values starting with 1
. So your ALTER TABLE
statement is implicitly using a default value of 1
. See my earlier comment for more background on that.
Once we properly support MySQL enum
values, we would also correctly control the default value.
Steps to reproduce
Almost everything works with MySql enums when you change the column type with the following method that you call for every property in your context:
The issue
Persistence and migrations work fine here. The limitation is on querying as by default MySql will return a string for this column and EF blows up trying to convert that to an int. All that needs to be done for the simple scenario is to either pre-cast these columns to int when querying, and/or handling the string => enum conversion. I'm not sure which approach would be easier, but again, I'm not looking for full complete enum support, just enough hooks to make it usable.
I'm happy to help contribute, but would ask for recommendation on how to make one of these options work.
Further technical details
MySQL version: 5.7 Operating system: Ubuntu Pomelo.EntityFrameworkCore.MySql version: 1.0