TrackableEntities / EntityFrameworkCore.Scaffolding.Handlebars

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

Setting HasDefaultValue for Enumeration in DBContext generation #242

Closed mwmccallum closed 5 months ago

mwmccallum commented 6 months ago

I am looking for some suggestions on how to set HasDefaultValue to an Enumeration class default value.

DBContext generation sets the entity like this: entity.Property(e => e.acti_acty_number).HasDefaultValue(0);

Need to set it like this. entity.Property(e => e.acti_acty_number).HasDefaultValue(ActivityType.Unassigned);

Similar issue arises with a Virtual Many to Many table that has an Enumeration as one of the keys. For my specific model, I only have one instance of this occurring, so commenting out the generated entry and using the in my partial class XXXXContext.OnModelCreatingPartial wasn't really a big deal.

However, with Enumeration HasDefaultValue I currently have more than 30 of these. Having to search an replace them all is not really feasible. In EF6 and earlier when HasDefaultValue wasn't being generated in DBContext so I never had this issue. Now with HasDefaultValue being generated, it fails to valid the XXXXContext class when it loads because it cannot set an INT to my Enumeration type.

Options I have considered:

  1. Prevent scaffold task from setting HasDefaultValue on the property when processing HbsCSharpDbContextGeneratorGenerateProperty method. This would require something like ExcludedTables file for which Table.Column to ignore setting HasDefaultValue for. Then in my partial class XXXXContext.OnModelCreatingPartial, explicitly set it here and always handle them manually.

  2. ScaffoldingDesignTimeServices is where with PropertyTransformer, I change the PropertyType to the Enumeration. Ideally this would be where I would want to be able to set a DefaultValue, using EntityPropertyInfo adding a new property (DefaultValue) that would hold default value to put in the DBContext generated file (i.e. ActivityType.Unassigned). DefaultValue of EntityPropertyInfo would most likely have to be an object, and the Cast it by PropertyType to set like this: "entity.Property(e => e.acti_acty_number).HasDefaultValue(ActivityType.Unassigned);" I guess object could be cast with .ToString as part of building a formatted string expression.

Using option 2, would yield possibility to do something like this, inside of PropertyTransformer. Since it would be only be attached to those entity properties that had a database default, it could be put on all possible properties in this method.

` private EntityPropertyInfo PropertyTransformer ( IEntityType t, EntityPropertyInfo f ) { string propertyType = f.PropertyType; string propertyName = f.PropertyName;

if ( f.PropertyName == "acty_number" || f.PropertyName.EndsWith ( "_acty_number" ) )
{
    propertyType = nameof ( ActivityType );
    defaultValue = ActivityType.Unknown;
}

return new EntityPropertyInfo ( propertyType, propertyName, f.PropertyIsNullable );

}

`

With either option, a list like ExcludedTables.txt would be required (i.e. ExcludeDefaultValues.txt ) which would have a format of Table.Column (i.e. activity.acti_acty_number) that would need to be split on the period and matched to the EntityType and Property for which it applies. This would require and EntityTypeTransformationService that would hold a Dictionary<tablename,columnname> used to match Entity and Property and either prevent the HasDefaultValue from being added (Option 1) or setting HasDefaultValue (Option 2) with EntityPropertyInfo.DefaultValue on match to the Dictionary.

As I noted, I am looking for suggestions what how this could best be implemented within the structure of this component.

Thanks, Mike

mwmccallum commented 6 months ago

I spent time over the holidays pondering the solution and decided best way would be the add properties to the EntityPropertyInfo.cs to support the functionality. There would be 2 additional properties added to support Enumerations.

  1. isEnumPropertyType boolean to determine if this is an Enumeration. This is needed for many to many virtual tables in DBContext.cs.
  2. propertyDefaultEnumValue this would be the string representation of the default value for the Enumeration (i.e. Enum.DefaultValue)

Class changes would include the following: HbsCSharpDbContextGenerator.cs:

HbsCSharpEntityTypeGenerator.cs and HbsTypeScriptEntityTypeGenerator.cs:

IEntityTypeTransformationService.cs and HbsEntityTypeTransformationServiceBase.cs:

Finally, in my ScaffoldingDesignTimeServices.cs I have implemented the following code to set these new properties in EntityPropertyInfo.cs.

` private EntityPropertyInfo PropertyTransformer ( IEntityType t, EntityPropertyInfo f ) { string propertyType = f.PropertyType; string propertyName = f.PropertyName; bool IsEnumPropertyType = false; string propertyDefaultEnum = null;

// Sections of code missing for brevity

#region ENUM Properties
if ( f.PropertyName == "acty_number" || f.PropertyName.EndsWith ( "_acty_number" ) )
{
  IsEnumPropertyType = true;
  propertyType = nameof ( ActivityType );
  propertyDefaultEnum = $"{propertyType}.{ActivityType.Unassigned}";
}
#endregion 

// Sections of code missing for brevity

return new EntityPropertyInfo ( propertyType, propertyName, f.PropertyIsNullable
  , IsEnumPropertyType, propertyDefaultEnum );

} `

I use "propertyType = nameof ( ActivityType );" followed by "propertyDefaultEnum = $"{propertyType}.{ActivityType.Unassigned}";" so that I leverage intellisense and get compile errors for typeing mistakes.

Since there is not existing schema in the test NorthwindsSlim schema, this new functionality cannot be tested. I did do all testing against my project schema and all of my test there process successfully.

With greater complexity being added to EF, we should probable consider using AdventureWorks database schema for future testing. It has both multiple schemas and database triggers. Not sure if it has any advanced enumerations (i.e. strings instead of integers). Otherwise come up with a small schema that does have each of these more complex situations.

I will submit a PR with the above updates shortly. This PR would also have the items I submitted earlier, that have not been merged yet, since I am working off the same branch.

If any issues are identified, please let me know.

Thanks, Mike