Design Meeting Notes - October 11, 2012

Lightweight conventions

[Aka set-based configuration/batch configuration/bulk configuration. Given where the design is evolving we’re back to calling these lightweight conventions again.]

As previously stated, it is important that explicit configuration of an entity/property should take precedence over batch/bulk/set configuration. Also, unlike single entity configuration, using batch/bulk/set configuration does not add types to the model. Finally, not all types in the model have configurations associated with them at the time that the calls to the API is made, which makes the implementation as configuration changes hard. All this together means that they fit both the mental model for conventions better than configurations and are probably best implemented as conventions. We are therefore leaning towards making the Conventions.Add call be the entry point and calling these lightweight conventions.

It is important that when using lightweight conventions the developer doesn’t have to figure out where to insert the conventions in the convention list—they should just have to call Add and it will work.

The following table shows mock-ups of how various scenarios are handled by the existing, single-entity fluent API, by the full conventions infrastructure, and by possible options for lightweight conventions. Notes are below the table.

Fluent API/Full conventions Lightweight conventions

modelBuilder
    .Entity<MyEntity>()
    .Property(e => e.MyDecimal)
    .HasPrecision(8, 4);

 

// Full convention
public void Apply(
    PropertyInfo property,
    Func<DecimalPropertyConfiguration> configuration)
{
 
   configuration().Scale = 4;
}


 

//Casting (See {Note 1})
modelBuilder.Conventions
.Add(
    entities => entities
        .Properties()
        .Configure(
            propConfig =>
            ((DecimalTypeConfig)propConfig).Scale = 4));

//OfType method          
modelBuilder.Conventions
    .Add(
        entities => entities
            .Properties()
            .OfTypeDecimal()
            .Configure(propConfig => propConfig.Scale = 4));
 
//Type in root property method
modelBuilder.Conventions
    .Add(
        entities => entities
            .DecimalProperties()
            .Configure( propConfig => propConfig.Scale = 4));

//Flatten config so all possibilities are on all configs
modelBuilder.Conventions
    .Add(
        entities => entitites
            .Properties()
            .Where(p => p.PropertyType == typeof(decimal))
            .Configure(config => config.Scale = 4));

modelBuilder
    .Entity<MyEntity>()
    .HasKey(e => e.Key);

// Full convention
public void Apply(
    Type type,
    Func<EntityTypeConfiguration> configuration)
{
    var keyProperty = type.GetProperty("Key");

    if (keyProperty != null)
    {
        configuration().Key(keyProperty);
    }
}

See {Note 2}

modelBuilder.Conventions
    .Add(
        entitites => entitites
            .Properties()
            .Configure(config => config.IsKey());

modelBuilder
    .Entity<MyEntity>()
    .ToTable("MY_ENTITY");

// Full convention
public void Apply(
    Type type,
    Func<EntityTypeConfiguration> configuration)
{
        configuration().ToTable(
            Regex.Replace(
                type.Name,
                "([a-z])([A-Z])",
                "${1}_${2}").ToUpper());
}

modelBuilder.Conventions
    .Add(
        entities => entities
            .Configure(
                config => config
                    .ToTable(
                        Regex.Replace(
                            config.ClrType.Name,
                            "([a-z])([A-Z])","${1}_${2}")
                            .ToUpper());

modelBuilder
    .Entity<MyEntity>()
    .Property(e => e.MyDateTime)
    .HasColumnType("datetime2");

// Full convention
public void Apply(
    PropertyInfo property,
    Func<DateTimePropertyConfiguration> configuration)
{   
        configuration().ColumnType
            = "datetime2";
}

See {Note 3}

modelBuilder.Conventions
    .Add(
        entities => entitites
            .Properties()
            .Where(p => p.PropertyType == typeof(DateTime))
            .Configure(c => c.ColumnType = “datetime2”);

//entitites.Where().Ignore();

//entities.Properties.Where().Ignore();

entities.Where().Configure(c => c.Ignore()); // See {Note 4}

entities.Properties().Configure(c => c.Ignore());

modelBuilder
    .Entity<MyEntity>()
    .Property(e => e.MyString)
    .IsUnicode(false);

 

// Full convention
public void Apply(
    PropertyInfo property,
    Func<StringPropertyConfiguration> configuration)
{
        configuration().IsUnicode = false;
}


 

modelBulder.Conventions
    .Add(
        entities =>  entitites
            .Properties()
            .Configure(config => config.IsUnicode = false);

Notes:

  • {General notes}
    • It is okay for lightweight conventions to not cover all possible cases; full conventions provide building blocks for that.
    • The Conventions.Add method is now overloaded to take something like ConventionBuilder instance.
    • Do we even need the Configure method or should we have a fluent API after Properties()/Entities()?
      • Decision: Keep the Configure method; this is a fairly advanced API anyway and it makes doing multiple configurations in one call easier
    • Should configuration members be methods like fluent API or more traditional properties?
      • Decision: Fluent API is write-only so methods make sense. This is read-write, so use more traditional approach.
    • The configuration passed to the lightweight convention must handle not overwriting explicit configuration. The developer should not have to do this.
      • This can be done by using a proxy for the configuration that checks whether or not configuration has already been explicitly set.
  • {Note 1} Should the API provide the ability to filter by property type?
    • Using a generic doesn’t really work because, for example, there is no way to use a generic constraint to change the API based on something like “decimal”
    • Using method names like OfTypeDecimal or DecimalProperties could work, but it doesn’t buy very much simplicity and also makes it harder for configuration that could be applied to multiple types of property but not to all.
    • Decision: use flattening and make all configuration options available on all properties regardless of type
      • If configuration can not be applied to property (e.g. Precision to string property) then it will be a no-op
      • If configuration can be applied but it results in an invalid model then the invalid model will signal the error
      • If configuration can be applied to multiple properties but only properties of a certain type should be configured (e.g. MaxLength to strings/binaries) then developer will have to filter with Where or similar.
  • {Note 2} Normally keys are configured from the entity, not on the property. Do we want to do the same here?
    • Configuring on the entity allows composite keys with order to be easily configured.
    • But common case for convention is to make any property that matches a pattern (e.g. ends with Key) be a key. This is harder to do if API is based off entity because property matching would have to be done fore every property in entity.
    • Decision: consider this to be like adding the KeyAttribute annotation. It is therefore on the property and the common case is easy. Composite key configuration can be done explicitly or with a full convention.
      • Also, allow IsKey method to take an index for ordering of composite keys. This means if every entity has something like a federation ID that is always the first then it can still be done with the simple API
      • Potentially the With method could make this easier without needing IsKey for the property; Diego to come up with ideas.
  • {Note 3} Where
    • Should we have Where method or make all filtering be done in Configure method?
      • Decision: For now we will keep Where method; it really helps the experience.
    • Should Where take PropertyInfo or configuration object?
      • Decision: PropertyInfo since this is more intuitive and matches better to existing mental model for Where
    • What about using a method like With instead that would allow the results of evaluating the Where predicate to be used in the closure?
      • Diego to share ideas on this
  • {Note 4} Should Ignore for an entity be top-level or on the configuration
    • Probably on the configuration, but model discovery implications of Ignore may make this hard to implement either way

Last edited Dec 14, 2012 at 6:12 PM by ajcvickers, version 2

Comments

No comments yet.