New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Model Definition: Custom conventions #214

Open
rowanmiller opened this Issue May 22, 2014 · 13 comments

Comments

Projects
None yet
8 participants
@rowanmiller
Member

rowanmiller commented May 22, 2014

The following should be done before making the convention API public:

  • Rename the Apply methods to be more descriptive and make the return types consistent, allowing to stop the execution of the following conventions while still returning the model element
  • Add more convention interfaces to cover the obvious gaps
  • Split OnPrincipalEndChanged into OnPrincipalEndChanged OnPrincipalEndConfigurationSourceChanged
  • Create public interfaces that the internal model builders implement: IConventionModelBuilder and IDataAnnotationModelBuilder this will allow cleaner API, without the explicit ConfigurationSource parameter.
  • Add provider-specific extension methods for mutable metadata interfaces
  • Rename and move RelationalAnnotations and RelationalAnnotationsBuilder to Core
  • Refactor the relational ValueGenerated methods to be easier to extend
  • Remove unused methods on provider classes like SqlServerPropertyBuilderAnnotations.ColumnName
  • Make model element references consistent (e.g. Property vs IProperty vs string vs PropertyInfo)
  • Review what can be handled by conventions that is currently done by migrations or the model itself. Like making the ownership FK the PK.
  • Add convention type for configuring a type as owned and use it to remove ambiguous ownerships
  • Combine BaseTypeDiscoveryConvention and DerivedTypeDiscoveryConvention as well as CacheCleanupConvention and ModelCleanupConvention
  • Reconsider throwing instead of silently failing when a non-explicit configuration call is invalid. This would require adding quiz API ('CanSet'). Otherwise be clear about the method purpose (methods should be prefixed with 'Try')
  • Track the model elements as they are modified by conventions, so a call to a convention never returns an element that is no longer in the model.
  • All methods that could trigger conventions should have a way to delay the convention execution.
  • Don't store delayed convention executions if no conventions are registered
  • When executing delayed conventions prune redundant ones
  • Use reference counting to avoid scanning the model for unused elements. When a configuration source of an aspect is changed because it is referenced from another element this reference should be stored in a collection, so if all references are removed the aspect can be removed as well or its configuration source downgraded to what it was previously
  • Call member added/removed for each entity type in the hierarchy, so they can be handled in the same way when the base type changes
  • Consider adding provider-specific or annotation-specific convention types, so they don't need to examine all annotations
  • Pass CoreConventionSetBuilderDependencies to the constructor of all conventions

@rowanmiller rowanmiller added this to the 1.0.0 milestone May 22, 2014

@rowanmiller rowanmiller changed the title from Model Definition: Provide API for customizing conventions to Model Definition: Provide API for custom conventions Oct 9, 2014

@rowanmiller rowanmiller changed the title from Model Definition: Provide API for custom conventions to Model Definition: Custom conventions Oct 9, 2014

@rowanmiller rowanmiller removed the pri0 label Nov 26, 2014

@rowanmiller rowanmiller modified the milestones: Backlog, 7.0.0 Nov 26, 2014

@sthiakos

This comment has been minimized.

Show comment
Hide comment
@sthiakos

sthiakos Dec 26, 2015

This was useful for me in the following Scenarios:

  • Undo default conventions applied by EF using DbModelBuilder
    ** Creating an IStoreModelConvention for AssociationType to remove underscore from FK names,
    ** Creating an IStoreModelConvention for EntitySet to remove underscore from m-2-m join tables/dbsets (not applicable right now to EF Core).
  • Dictate Entity Identity Behaviors - using the modelBuilder.Types() method:
    ** Auto-generation based on whether it's a Data-Dictionary/Lookup item (e.g. BlogType - static) vs an Entity (Blog - autogen). They are distinguished based on interface implementations.
  • Decouple code naming standards and DB naming standards - using the modelBuilder.Properties() method:
    ** Give a common Class property (e.g. "StateId") but an explicit db column name (e.g. "BlogStateId"). Same goes for "Id" in code but "{Type}Id" in database.

Doing this once I was able to govern the entity definitions without requiring explicit entity declaration. I'm not advocating the same implementation, but do feel custom/override conventions are valuable.

Cheers,

Steve

sthiakos commented Dec 26, 2015

This was useful for me in the following Scenarios:

  • Undo default conventions applied by EF using DbModelBuilder
    ** Creating an IStoreModelConvention for AssociationType to remove underscore from FK names,
    ** Creating an IStoreModelConvention for EntitySet to remove underscore from m-2-m join tables/dbsets (not applicable right now to EF Core).
  • Dictate Entity Identity Behaviors - using the modelBuilder.Types() method:
    ** Auto-generation based on whether it's a Data-Dictionary/Lookup item (e.g. BlogType - static) vs an Entity (Blog - autogen). They are distinguished based on interface implementations.
  • Decouple code naming standards and DB naming standards - using the modelBuilder.Properties() method:
    ** Give a common Class property (e.g. "StateId") but an explicit db column name (e.g. "BlogStateId"). Same goes for "Id" in code but "{Type}Id" in database.

Doing this once I was able to govern the entity definitions without requiring explicit entity declaration. I'm not advocating the same implementation, but do feel custom/override conventions are valuable.

Cheers,

Steve

@rowanmiller

This comment has been minimized.

Show comment
Hide comment
@rowanmiller

rowanmiller Dec 28, 2015

Member

Some of these types of things can be achieved by iterating over the model that is created by convention. This was not possible in EF6 because we collected a set of configuration during OnModelCreating and then used that plus conventions to build the model. But in EF Core we build an initial model from conventions and then pass that to OnModelCreating.

Of course, this means you need to be mindful of ordering. For example, if a type wasn't included in the model by convention, then you need to make sure you add it before you iterate over the model and assign table names.

Here is an example of appending an s to the table name.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  foreach (var entity in modelBuilder.Model.GetEntityTypes())
  {
    modelBuilder.Entity(entity.Name).ToTable(entity.Name + "s");
  }
}
Member

rowanmiller commented Dec 28, 2015

Some of these types of things can be achieved by iterating over the model that is created by convention. This was not possible in EF6 because we collected a set of configuration during OnModelCreating and then used that plus conventions to build the model. But in EF Core we build an initial model from conventions and then pass that to OnModelCreating.

Of course, this means you need to be mindful of ordering. For example, if a type wasn't included in the model by convention, then you need to make sure you add it before you iterate over the model and assign table names.

Here is an example of appending an s to the table name.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
  foreach (var entity in modelBuilder.Model.GetEntityTypes())
  {
    modelBuilder.Entity(entity.Name).ToTable(entity.Name + "s");
  }
}
@sthiakos

This comment has been minimized.

Show comment
Hide comment
@sthiakos

sthiakos Dec 29, 2015

Thanks for the info Rowan,

I'll just need to explicitly load my entities in this method for my domain assembly:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var entityTypes = myDomainAssembly.DefinedTypes
        .Where(ti => ti.IsClass && ti.IsPublic && !ti.IsAbstract && !ti.IsNested)

    foreach (var entityType in entityTypes)
    {
        modelBuilder.Entity(entityType).ToTable(entity.Name + "s");
    }
}

This makes me think that perhaps an OnEntityModelCreating(EntityTypeBuilder entityTypeBuilder) may help with conventions and keep things dynamic.

Steve

sthiakos commented Dec 29, 2015

Thanks for the info Rowan,

I'll just need to explicitly load my entities in this method for my domain assembly:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    var entityTypes = myDomainAssembly.DefinedTypes
        .Where(ti => ti.IsClass && ti.IsPublic && !ti.IsAbstract && !ti.IsNested)

    foreach (var entityType in entityTypes)
    {
        modelBuilder.Entity(entityType).ToTable(entity.Name + "s");
    }
}

This makes me think that perhaps an OnEntityModelCreating(EntityTypeBuilder entityTypeBuilder) may help with conventions and keep things dynamic.

Steve

@totht91

This comment has been minimized.

Show comment
Hide comment
@totht91

totht91 Jan 5, 2016

I see that this issue is not a priority issue, but will the custom conventions be implemented before RC2, or just before RTM? (I hope it will be at least in RTM)

totht91 commented Jan 5, 2016

I see that this issue is not a priority issue, but will the custom conventions be implemented before RC2, or just before RTM? (I hope it will be at least in RTM)

@ErikEJ

This comment has been minimized.

Show comment
Hide comment
@ErikEJ

ErikEJ Jan 5, 2016

Contributor

According to the current roadmap it will be in 7.0.2 (?) https://github.com/aspnet/EntityFramework/wiki/Roadmap

Contributor

ErikEJ commented Jan 5, 2016

According to the current roadmap it will be in 7.0.2 (?) https://github.com/aspnet/EntityFramework/wiki/Roadmap

@totht91

This comment has been minimized.

Show comment
Hide comment
@totht91

totht91 Jan 5, 2016

Ohh I missed this. Thank you.

totht91 commented Jan 5, 2016

Ohh I missed this. Thank you.

@ajcvickers

This comment has been minimized.

Show comment
Hide comment
@ajcvickers

ajcvickers Mar 3, 2016

Member

Consider a way to get from IMutableEntityType (or similar) to the InternalEntityTypeBuilder (or similar) so that changes can be flagged as "by convention" when making changes to an IMutableEntityType. See comments on PR #4688.

Member

ajcvickers commented Mar 3, 2016

Consider a way to get from IMutableEntityType (or similar) to the InternalEntityTypeBuilder (or similar) so that changes can be flagged as "by convention" when making changes to an IMutableEntityType. See comments on PR #4688.

@AndriySvyryd AndriySvyryd self-assigned this Apr 1, 2016

AndriySvyryd added a commit that referenced this issue Mar 1, 2017

Refactor ConventionDispatcher to allow delayed execution of conventio…
…ns and tracking objects modified by conventions.

  When set on ConventionDispatcher ConventionScope captures the conventions to be executed.
  ConventionBatch sets a ConventionScope and executed the stored conventions when it's run or disposed.
  When conventions captured in a ConventionScope are executed any triggered conventions are captured in a new ConventionScope to avoid keeping all the convention chain on the stack.
  This changes the order of convention execution which exposed some issues in code and tests
Allow specifying the dependend end without specifying the FK properties,
Throw when the same property is specified several times when adding a key, foreign key or indexes.
Remove Mock usage from ConventionDispatcherTest

Part of #214
@divega

This comment has been minimized.

Show comment
Hide comment
@divega

divega Apr 27, 2017

Member

Regarding

When executing delayed conventions prune redundant ones.

We believe that this could help improve the performance of building the model quite a bit. Currently our benchmarks show that building the AdventureWorks model is marginally slower than in EF6:

InitializationTests.BuildModel_AdventureWorks

image

cc @AndriySvyryd

Member

divega commented Apr 27, 2017

Regarding

When executing delayed conventions prune redundant ones.

We believe that this could help improve the performance of building the model quite a bit. Currently our benchmarks show that building the AdventureWorks model is marginally slower than in EF6:

InitializationTests.BuildModel_AdventureWorks

image

cc @AndriySvyryd

@ajcvickers

This comment has been minimized.

Show comment
Hide comment
@ajcvickers

ajcvickers Mar 28, 2018

Member

See #11404 for details of internal conventions related API that are used by providers.

Member

ajcvickers commented Mar 28, 2018

See #11404 for details of internal conventions related API that are used by providers.

@ajcvickers

This comment has been minimized.

Show comment
Hide comment
@ajcvickers

ajcvickers Apr 23, 2018

Member

Also consider #11738 when working on this issue.

Specifically, the comment here: #11738 (comment)

Member

ajcvickers commented Apr 23, 2018

Also consider #11738 when working on this issue.

Specifically, the comment here: #11738 (comment)

@divega

This comment has been minimized.

Show comment
Hide comment
@divega

divega May 17, 2018

Member

@AndriySvyryd can you confirm if this is the right issue for the item "Public conventions (so that providers don't need internal code)" in our planning?

Member

divega commented May 17, 2018

@AndriySvyryd can you confirm if this is the right issue for the item "Public conventions (so that providers don't need internal code)" in our planning?

@ajcvickers

This comment has been minimized.

Show comment
Hide comment
@ajcvickers

ajcvickers May 21, 2018

Member

Closed #11404 and #11405 as duplicates of this since providers will need to update to the new public APIs for 3.0,

Member

ajcvickers commented May 21, 2018

Closed #11404 and #11405 as duplicates of this since providers will need to update to the new public APIs for 3.0,

@MeikelLP

This comment has been minimized.

Show comment
Hide comment
@MeikelLP

MeikelLP Aug 8, 2018

In case someone comes across this and requires a workaround for snake case table & property names... Here is my workaround (may help the implementation or extensions):

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    foreach (var entity in modelBuilder.Model.GetEntityTypes())
    {
        // table names
        modelBuilder.Entity(entity.Name).ToTable(ToSnakeCase(entity.ShortName() + 's'));
        
        // properties
        var props = GetType().Assembly
            .GetType(entity.Name)
            .GetProperties()
            .Where(x => 
                x.GetCustomAttribute<NotMappedAttribute>() == null &&
                (
                    x.PropertyType.IsPrimitive ||
                    (
                        typeof(Nullable<>).IsAssignableFrom(x.PropertyType) && x.PropertyType.GenericTypeArguments[0].IsPrimitive
                    ) ||
                    x.PropertyType == typeof(string)
                )
            );
        foreach (var prop in props)
        {
            modelBuilder.Entity(entity.Name).Property(prop.Name).HasColumnName(ToSnakeCase(prop.Name));
        }
    
    }
}

private string ToSnakeCase(string input)
{
     var returnString = "";
     foreach (var chr in input)
     {
         if (chr.ToString().ToUpper() == chr.ToString())
         {
              // is uppercase character
             returnString += '_' + chr.ToString().ToLower();
         }
         else
         {
             returnString += chr;
         }
    }

    returnString = returnString.TrimStart('_');
    return returnString;
}

MeikelLP commented Aug 8, 2018

In case someone comes across this and requires a workaround for snake case table & property names... Here is my workaround (may help the implementation or extensions):

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    foreach (var entity in modelBuilder.Model.GetEntityTypes())
    {
        // table names
        modelBuilder.Entity(entity.Name).ToTable(ToSnakeCase(entity.ShortName() + 's'));
        
        // properties
        var props = GetType().Assembly
            .GetType(entity.Name)
            .GetProperties()
            .Where(x => 
                x.GetCustomAttribute<NotMappedAttribute>() == null &&
                (
                    x.PropertyType.IsPrimitive ||
                    (
                        typeof(Nullable<>).IsAssignableFrom(x.PropertyType) && x.PropertyType.GenericTypeArguments[0].IsPrimitive
                    ) ||
                    x.PropertyType == typeof(string)
                )
            );
        foreach (var prop in props)
        {
            modelBuilder.Entity(entity.Name).Property(prop.Name).HasColumnName(ToSnakeCase(prop.Name));
        }
    
    }
}

private string ToSnakeCase(string input)
{
     var returnString = "";
     foreach (var chr in input)
     {
         if (chr.ToString().ToUpper() == chr.ToString())
         {
              // is uppercase character
             returnString += '_' + chr.ToString().ToLower();
         }
         else
         {
             returnString += chr;
         }
    }

    returnString = returnString.TrimStart('_');
    return returnString;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment