TrackableEntities / EntityFrameworkCore.Scaffolding.Handlebars

Scaffold EF Core models using Handlebars templates.
MIT License
208 stars 53 forks source link

[Feature] Make IContextTransformationService available when using Handlebars transformers #198

Open tonysneed opened 2 years ago

tonysneed commented 2 years ago

Add IContextTransformationService to HbsCSharpDbContextGenerator so that the context can be made available when using the other transforms:

entityNameTransformer: MapEntityName,
constructorTransformer: MapPropertyInfo,
propertyTransformer: MapPropertyInfo,
navPropertyTransformer: MapNavPropertyInfo,
entityFileNameTransformer: MapEntityName
tonysneed commented 2 years ago

Transfer https://github.com/TrackableEntities/EntityFrameworkCore.Scaffolding.Handlebars/issues/188#issuecomment-994884840 to this issue.

I am now looking at the second part of my request and wanted to clarify why I wanted it. When I was writing the design time transformer class I created a JSON lookup file for my mappings:-

    "Name": "EntitiesOne:Aircraft",
    "NameOverride": "MyAircraft",
    "Mappings": [
        {
            "Name": "Livery",
            "NameMapping": "MyLivery"
        }
    ]

To correctly select the mappings:-

Entity Name Mappings require the context:entityname PropertyName Mappings require context:entityname:propertyname So I need context and so I need the TransformContextFileName method to be called early on in my Transformer class so I can store the context locally. Currently it is only called when the model is generated in the HbsCSharpModelGenerator. Hope this makes sense.

tonysneed commented 2 years ago

@gpender I am trying to wrap my head around why you need to call ContextTransformationService.TransformContextFileName in the WriteCode method of HbsCSharpDbContextGenerator and how this would enable you to access the transformed context file name in your transformer class.

Perhaps you would be kind enough to create a small repo containing an example transformer class that shows how calling ContextTransformationService.TransformContextFileName in HbsCSharpDbContextGenerator surfaces the context file name so that you can use it in your other transform methods?

gpender commented 2 years ago

Hi @tonysneed and Happy New Year :) sorry for not getting back to you sooner. In our Use Case application we have multiple existing SQLSERVER databases which we are migrating and there are tables with duplicate names.

So for example we have the table Aircraft appearing in 2 databases (The Aircraft tables are both different) DatabaseA with table Aircraft DatabaseB with table Aircraft

To scaffold the entities correctly I would like to have a single mappings.json lookup file where we have all the mappings for all databases:-

[
    {
        "Name": "DatabaseA:Aircraft",
        "NameOverride": "CivilianAircraft",
        "Mappings": [
            {
                "Name": "Tail",
                "NameMapping": "TailLivery"
            }
        ]
    },
    {
        "Name": "DatabaseB:Aircraft",
        "Mappings": [
            {
                "Name": "Type",
                "NameMapping": "AircraftType"
            }
        ]
    }
]

My transformer class uses this single json file to make the transforms and needs the current context (database) to fully qualify the entries.

In the code below you can see that I store the _context locally when it is called and then use it to access the entries in the mappings.json file. I hope this makes sense :)

namespace HandlebarsScaffolding
{
    public class ScaffoldingDesignTimeServices : IDesignTimeServices
    {
        private string _entityName, _context;
        private readonly EntityMappings _mappings;
        public ScaffoldingDesignTimeServices()
        {
            try
            {
                _mappings = File.ReadAllText($@"{Environment.CurrentDirectory}\Mapping\mappings.json").FromJson<EntityMappings>();
                Console.WriteLine($@"Reading {Environment.CurrentDirectory}\Mapping\mappings.json");
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
                throw;
            }
        }
        public void ConfigureDesignTimeServices(IServiceCollection services)
        {
            services.AddHandlebarsScaffolding(options);
            services.AddHandlebarsTransformers(

                contextFileNameTransformer: (c) =>
                {
                    _context = Path.GetFileNameWithoutExtension(c);
                    return c;
                },
                entityNameTransformer: MapEntityName,
                constructorTransformer: MapPropertyInfo,
                propertyTransformer: MapPropertyInfo,
                navPropertyTransformer: MapNavPropertyInfo,
                entityFileNameTransformer: MapEntityName);
        }
        private string MapEntityName(string entityName)
        {
            _entityName = entityName;
            var nameOverride = _mappings.FirstOrDefault(e => e.Name == $"{_context}:{_entityName}")?.NameOverride;
            return nameOverride ?? entityName;
        }
        private EntityPropertyInfo MapNavPropertyInfo(EntityPropertyInfo e)
        {
            return new EntityPropertyInfo(MapPropertyTypeName(e.PropertyType), MapPropertyName(e.PropertyName, false));
        }
        private EntityPropertyInfo MapPropertyInfo(EntityPropertyInfo e)
        {
            return new EntityPropertyInfo(MapPropertyTypeName(e.PropertyType), MapPropertyName(e.PropertyName));
        }
        private string MapPropertyTypeName(string propertyTypeName)
        {
            var propertyTypeNameOverride = _mappings.FirstOrDefault(e => e.Name == $"{_context}:{propertyTypeName}")?.NameOverride;
            return propertyTypeNameOverride ?? propertyTypeName;
        }
        private string MapPropertyName(string propertyName, bool useEntityNameFilter = true)
        {
            IEnumerable<Mapping> entityMappings = null;
            if (useEntityNameFilter)
                entityMappings = _mappings.Where(m => m.Name == $"{_context}:{_entityName}").SelectMany(m => m.Mappings);
            else
                entityMappings = _mappings.Where(m=>m.Name.Split(':')[0] == _context).SelectMany(m => m.Mappings);
            var map = entityMappings.FirstOrDefault(m => m.Name == propertyName);
            return map != null ? map.NameMapping : propertyName;
        }
    }
}
tonysneed commented 2 years ago

Been super busy, but I haven’t forgotten about this. Thanks for patience and please stay tuned.