Pre-convention model configuration
ajcvickers opened this issue · 4 comments
Doing an initial discovery of the model based only on DbSet roots was a reasonable approach when it was reasonably easy to distinguish likely scalar properties from likely navigation properties. However, as we allow more an more types to be mapped it has become increasingly problematic to
- Exclude a type as an entity type and therefore avoid trying to bring it and all its properties into the model
- Revert a type from being an entity type when a value converter is found or the type is ignored.
This results in both bugs and additional overhead.
Therefore, this issue is about finding ways to enhance model building so that it can more efficiently figure out what is an entity type and what is not. Some ideas:
- Some up-front configuration that defines either types that are entity types, or types that are not entity types. This would be provided before model building begins, possibly using model customization or lifecycle hooks.
- Up-front configuration for value converters, which would provide partial info on types that are not entity types.
The initial work here is to investigate and have some initial design discussions about ways we could go.
The up-front configuration should be non-interactive (i.e. no conventions will be running before it is finished and there won't be a way to get the current model). Thus it requires new API separate from ModelBuilder
(but still following the same patterns).
Proposal: DbContext.OnModelBuiderCreating(ModelConfigurationBuilder configurationBuilder)
Also modelBuilder.Owned<>()
should be moved to the new API.
A more detailed proposal:
protected internal virtual void ConfigureConventions(ModelConfigurationBuilder configurationBuilder);
public class ModelConfigurationBuilder
{
public ModelConfigurationBuilder(ConventionSet conventions);
public ConventionSetBuilder Conventions { get; }
public virtual EntityTypesConfigurationBuilder Entities();
public virtual ModelConfigurationBuilder Entities(Action<EntityTypesConfigurationBuilder> buildAction);
public virtual EntityTypesConfigurationBuilder Entities(Type type);
public virtual ModelConfigurationBuilder Entities(Type type, Action<EntityTypesConfigurationBuilder> buildAction);
public virtual EntityTypesConfigurationBuilder SharedTypeEntities();
public virtual ModelConfigurationBuilder SharedTypeEntities(Action<EntityTypesConfigurationBuilder> buildAction);
public virtual EntityTypesConfigurationBuilder SharedTypeEntities(Type type);
public virtual ModelConfigurationBuilder SharedTypeEntities(Type type, Action<EntityTypesConfigurationBuilder> buildAction);
public virtual OwnedEntityTypesConfigurationBuilder OwnedEntities();
public virtual ModelConfigurationBuilder OwnedEntities(Type type);
public virtual OwnedEntityTypesConfigurationBuilder OwnedEntities(Type type);
public virtual ModelConfigurationBuilder OwnedEntities(Type type, Action<OwnedEntityTypesConfigurationBuilder> buildAction);
public virtual ModelConfigurationBuilder IgnoreAny(Type type);
public virtual PropertiesConfigurationBuilder Properties(Type propertyType);
public virtual ModelConfigurationBuilder Properties(Type propertyType, Action<PropertiesConfigurationBuilder> buildAction);
public virtual ServicePropertiesConfigurationBuilder ServiceProperties(Type propertyType);
public virtual ModelConfigurationBuilder ServiceProperties(Type propertyType, Action<ServicePropertiesConfigurationBuilder> buildAction);
public virtual ModelBuilder InitializeModelBuilder(ModelDependencies modelDependencies);
}
public class PropertiesConfigurationBuilder
{
[EntityFrameworkInternal]
public PropertiesConfigurationBuilder(PropertiesConfiguration property);
public virtual PropertiesConfigurationBuilder HaveAnnotation(string annotation, object? value);
public virtual PropertiesConfigurationBuilder HaveMaxLength(int maxLength);
public virtual PropertiesConfigurationBuilder HavePrecision(int precision, int scale);
public virtual PropertiesConfigurationBuilder HavePrecision(int precision);
public virtual PropertiesConfigurationBuilder AreUnicode(bool unicode = true);
public virtual PropertiesConfigurationBuilder HaveConversion(Type providerClrType);
public virtual PropertiesConfigurationBuilder HaveConversion(Type converterType, Type comparerType);
}
Calling these methods will have no immediate effect on the model. But when a type is discovered by convention any configuration specified for it or any base type or implemented interface will be applied at that point. The configuration specified using ModelConfigurationBuilder
is considered Explicit
Last one wins for multiple conflicting configurations for the same type.
An exception is thrown when base types have conflicting configurations, unless overridden by Fluent API.