5

Context:

I use Database-first approach to generate my entities with Entity Framework Core and with use of Scaffolding. Each time I change something in the database I run Scaffolding with -f parameter to rewrite generated entities. So far so good. In my database schema I have a few Lookup tables that serve the purpose as Enums for my other tables and those other tables has Foreign key on them.

Problem:

I would like to generate from these lookup tables Enums which I can use in my code. These enums should be populated by the data in the lookup table (Id, Value). Is something like this even possible ?

I saw that one can ignore those tables from Entity generation and link their own enums with use of value-conversions. But is this really the only way around ?


Example of my database schema:

TABLE: Category

Id Value
0 Cardio
1 Strength
2 Hyperthrophy

TABLE: Exercise

Id Name Category
0 Deadlift 1
1 Benchpress 1
2 Jogging 0

So from table Category I would like to generate simple Enum:

public enum Category {
    Cardio = 0,
    Strength = 1,
    Hyperthrophy = 2
}

and then I want it to be used in the entity Exercise like:

public partial class Exercise {
    public Exercise() {
    }

    public int Id { get; set; }
    public string Name { get; set; }
    public Category Category { get; set; }
}
Kebechet
  • 1,461
  • 15
  • 31
  • possible duplicate : https://stackoverflow.com/questions/50375357/how-to-create-a-table-corresponding-to-enum-in-ef-core-code-first – iSR5 Jul 07 '22 at 06:27
  • 1
    @ISR5 no, it's the reverse of that – JHBonarius Jul 07 '22 at 06:44
  • No tool can guess that a numeric column actually is a closed set of values, ie an enum, just by inspecting the table schema or even the values. It would make sense to scaffold enums only if the database product itself supported them. PostgreSQL and MySQL have *string* enum types though. SQL Server doesn't have enums – Panagiotis Kanavos Jul 07 '22 at 08:16
  • @panagiotis-kanavos Sure no tool can guess if the used database doesnt support it (in my case MSSQL). But isnt there a way to tell scaffolding such transformation is needed ? For example with overriding `OnConfiguring` or `OnModelCreating` and specifying that the table with its content should be represented as Enum ? – Kebechet Jul 07 '22 at 09:13
  • What enum would that be and what would be its values and labels? Even if you could tell the tool that a specific column is an "enum" you'd still have to manually enter the labels. PostgreSQL and MySQL do have enums, and NpgSql does map PostgreSQL enums to C# enums, but even there scaffolding only adds the mappings. You still have to create the enum yourself – Panagiotis Kanavos Jul 07 '22 at 09:22
  • In the databases that do support enums, it's up to the database provider to scaffold them. SQL Server doesn't have enums though, so the SQL Server provider doesn't do that. You could probably create an extension to modify `OnModelCreating` but you'd have to specify those columns in the command line too. You'd still have to create the enums yourself – Panagiotis Kanavos Jul 07 '22 at 09:23
  • EF Core 7 added scaffolding with custom T4 templates, which should at least allow you to define the foreign key column types. Not sure if it will allow defining the enum values from the current data. – Jeremy Lakeman Feb 20 '23 at 02:36

2 Answers2

1

While this is theoretically possible to generate code based on any data, there is no tool I know of what can do what you ask at this moment.

But the real question is: why would you want this? Or better is this really wat you want?

Scaffolding is based on the database scheme, not on the table contents. The table data is and should be assumed to be dynamic. Else (i.e. if the data is static) you should question why you put it in the database at all: you could and should just put it in the code. An enum is a rather static structure.

Entity Framework is an object mapper for a relational database. So you should just use the dynamic relationships and thus the key coupling.

JHBonarius
  • 10,824
  • 3
  • 22
  • 41
  • `The table data is and should be assumed to be dynamic` -I wouldnt say so. In case you have for example table `Status` with a few options like: `Created`, `In progress`, `In Test`, `Closed` and table `Ticket` where you use this status you have table with fixed/static options and its purpose is to be used as Foreign key. You simply use it for keeping normalization of values in that `Ticket` table and not allowing writing of other values there (in the `TicketStatus` column). If I understand it correctly. – Kebechet Jul 07 '22 at 07:44
  • 1
    @ssamko I would argue against it, because it's not _really_ static. You could add or remove a row at any time. So as it's your design and choice to consider the table "static", it's your responsibility to correctly map it in your code. – JHBonarius Jul 07 '22 at 07:51
  • 2
    @ssamko actually automatic scaffolding is more of a "beginner"/"quick start" feature. Once you're doing actual complex applications it quickly falls short. – JHBonarius Jul 07 '22 at 07:52
  • 1
    PostgreSQL and MySQL have a string enum type. [NpgSQL](https://www.npgsql.org/efcore/mapping/enum.html#scaffolding-from-an-existing-database) supports them but scaffolding seems to be tricky. The mappings will be generated but not the C# enums: `In the future it may be possible to scaffold the actual enum type (and with it the properties), but this doesn't happen at the moment.` – Panagiotis Kanavos Jul 07 '22 at 08:19
0

It's not impossible, but you need to know if it's worth it. I develop using database-first, so to generate my entities I always run the scaffold tool. When I needed to customize my entities, I overrode a method that the tool uses to generate the files.

See:

[SuppressMessage("Usage", "EF1001:Internal EF Core API usage.", Justification = "<Pending>")]
public class EntityTypeGenerator : CSharpEntityTypeGenerator
{

    public EntityTypeGenerator([NotNull] IAnnotationCodeGenerator annotationCodeGenerator,
          [NotNull] ICSharpHelper cSharpHelper) :
          base(annotationCodeGenerator, cSharpHelper)
    {
    }

    public override string WriteCode(IEntityType entityType,
           string @namespace,
           bool useDataAnnotations,
           bool useNullableReferenceTypes)
    {
        var code = base.WriteCode(entityType, @namespace,
                    useDataAnnotations, useNullableReferenceTypes);

        var oldString = "public partial class " + entityType.Name;
        var newString = "public partial class " + entityType.Name + ":
                             Repositories.Infra.EntityBase";

        var newCode = code.Replace(oldString, newString);

        if (entityType.Name == "Task")
        {
            oldString = "byte StatusId ";
            newString = "Business.Models.Tarefas.EnumStatusTask StatusId ";
            newCode = code.Replace(oldString, newString);
        }

        return newCode;
    }

}

For this to work I created a separate project just for the purpose of affecting entity generation. In it I referenced the library Microsoft.EntityFrameworkCore.Design.

enter image description here

I injected my generator class inside the DesignTimeServices class implementation

public class DesignTimeServices: IDesignTimeServices
{
    public void ConfigureDesignTimeServices(IServiceCollection serviceCollection)
    {
        serviceCollection.AddSingleton<ICSharpEntityTypeGenerator, EntityTypeGenerator>();
    }
}

In the main project (the runtime one, in this case the API), I referenced my new project and added the reference to the lib Microsoft.EntityFrameworkCore.Tools

teste

The following link can serve as a basis for understanding how it works and what you can do https://learn.microsoft.com/en-us/ef/core/cli/services

Gilberto Alexandre
  • 2,227
  • 1
  • 18
  • 20