Design Meeting Notes - November 15, 2012

Lightweight conventions

EF-generated tables/columns

Tables and columns that are generated by EF will not be processed by ToTable, HasColumnName, etc. This includes FK columns for independent associations, discriminator columns, and the tables/columns generated for many-to-many mappings. These things are not processed because the lightweight conventions are based on processing the object model and these things do not have direct analogues in the object model. The consequence of this is that a lightweight convention to conform to a database naming standard will require additional work (possibly outside the convention) to cover these cases.

We could add to the lightweight conventions to provide ways of doing this, but that adds complexity to what is supposed to be a simple API and it’s not clear that this adds enough value given the existing options to handle this:

  • Explicitly configure the places where these items are being generated. This should be fine if there are just a few places where these constructs are being used.
  • Write a model convention that processes the EDM store model and performs the renaming etc at that level. This is a more complex API, but has the power to cover every case.

Error messages

The error messages that come from the core express things in terms of XML and EDM, which is far removed from the Code First way of mapping. This is a well-known issue with Code First; it’s not just a conventions issue. It has been prohibitive to change this in the past but several things have changed recently:

  • The core and Code First are no longer completely separate and, in particular, the metadata unification work brings these two much closer together.
  • Once the metadata unification is done there will no longer be any XML in the Code First pipeline, so validation probably has to change anyway.
  • The APIs used for tracking XML line numbers and doing some schema validation are not available if we choose to support a PCL build.

This means we will probably need a non-XML store schema validator and should consider improving the error reporting at the same time.

Enum for store types

Should we have an enum or some similar mechanism for discovering SQL Server store types such as nvarchar, datetime2, etc.? Decision: probably not—this is server specific, some times are parameterized, and if you are mapping to a specific store type (which is when this would be used) then you probably know which store type you are mapping to anyway.

IsKey/HasKey

Summarizing the behaviors for the key APIs:

  • If a key has already been configured (by the fluent API or through data annotations) then no change will be made to the keys of the class.
  • HasKey works the same as on the fluent API, except that if the key for the class is already configured then it will do nothing.
  • IsKey works like the Key annotation. That is, if IsKey is called for multiple properties, then each of those properties gets added to the composite key for the entity. In such a case the key ordering must also be specified in some way—for example, by chaining a HasColumnOrder call.
  • We should also consider adding the IsKey method to the normal fluent API.

Reflection overloads

Some methods take a PropertyInfo or similar. We have added overloads of these that accept strings to avoid the need for people to manually find the PropertyInfo objects.

ToTable

A convention written to call ToTable on every entity type will have the effect of turning TPH mapping into TPT mapping. This can be useful, but also could catch people out. Things we considered:

  • Change the semantics of ToTable when used in a convention. We chose not to do this because divergent semantics make it harder for people to understand what will happen even if they already understand ToTable on the other API. Also, changing all mappings to TPT in this way can be useful.
    • If the TPT behavior is useful, we could provide a different API for this and also allow TPC and TPH to be specified. But we don’t want to change the semantics anyway, and TPC is much less common.
  • We could provide a means for people to easily discover if the entity is at the root of the hierarchy. This would allow ToTable to be only called on the root. We will try this.
    • It’s probably okay to only provide this in Configure and still allow the Where/Having APIs to use Reflection types

Related to this is that calling ToTable with the same name on several types in a hierarchy should result in TPH for those types and should not throw.

View generation API

This is a proposed API to perform view generation and provide output in a format other than code. This is a low-level API that would be used by a limited audience—mostly tooling such as EF Designer, the EF Power Tools, T4 template view generator, and possibly a compiled state implementation. (f view generation perf improvements pan out then it will be used even less frequently.)

The entry point is StorageMappingItemCollection obtained from the MetadataWorksapce. (MetadataWorkspace can be created independently or obtained from an ObjectContext.) For example, to generates views for all mappings:

public ReadOnlyCollection<MappingViews> StorageMappingItemCollection.GenerateViews()

An internal method is used to generate views for a single C-S container mapping:

internal MappingViews StorageEntityContainerMapping.GenerateViews()

The MappingViews class looks like this:

public class MappingViews // Maybe ContainerMappingViews or ViewsForContainerMapping? 
{
    public string ModelContainerName { get; } // EdmEntityContainerName
    public string StoreContainerName { get; } // StoreEntityContainerName
    public string MappingHash { get; } // HashOverMappingClosure
    public string ViewHash { get; } // HashOverAllExtentViews
    public Dictionary<string, string> Views { get; }
}

Notes:

  • Views should be '\n' and not '\r\n' separated - Xml new line normalization breaks HashOverAllExtentViews
  • We should make sure that the collection is read-only or explicitly allows modification if we want to support that.

Optional:

  • Make MappingViews StorageEntityContainerMapping.GenerateViews() public to enable generating views just for one C-S container mapping
  • Caveat: StorageEntityContainerMapping not currently public

Further extensions:

  • Accept views in this form instead instead of a class derived from System.Data.Entity.Core.Mapping.EntityViewContainer
  • Allow loading views one at a time instead all of them (can save memory)
  • Remove the ViewHash (required for the above)
  • Make Mapping Hash work regardless of the order of EntitySets, properties and so on. Can we use hashing algorithm from CodeFirst?
  • Could view generation code be in terms of CQTs, with only the serialized form being eSQL?
    • Note that previous investigations have shown that eSQL is a good serialized form and it may be beneficial to move more in this direction even internally

Conclusion:

We should have something like this, but it’s not clear exactly where it should go yet. In particular, it’s not clear whether this should be on the runtime or a design assembly. Ideally there should be a way to use this from tooling that works for all versions of EF without needing to bind to different assemblies explicitly.

Removal of Code Contracts

Based on previous discussion in the design meeting and with the Code Contracts team we have decided to remove use of Code Contracts. The vast majority of contracts use is for checking arguments for nulls/empty strings. There are various options for replacing these checks.

The inline approach

public void Foo(Bar bar)
{
    if (bar == null)
    {
        throw new ArgumentNullException("bar");
    }
}

Pros/cons:

  • Allows FxCop to pick up wrong name
  • Allows ReSharper to know that bar cannot be null post this statement
  • Quite a lot of code (but can use snippets or similar)
    • Note that current formatting/cleanup rules for curlies to be on separate lines here

The simple method approach

public void Foo(Bar bar)
{
    Check.NotNull(bar, "bar");
}

Pros/cons:

  • Small amount of code
  • Default FxCop rule won’t help keep string in sync, but could write a new one
  • ReSharper can be taught to recognize the pattern as well

The expression approaches

There are various patterns that use an expression tree optionally also with a check. These are significantly slower if the expression always has to be picked apart. If the expression is in addition to a regular check then the extra code/complexity doesn’t seem worthwhile.

Conclusion

We will use the simple method approach and write the FxCop rule.

Internal checks

Currently we compile out checks on the internal surface. This is because internal code should make sure it complies with the contract of the code it is calling and if it doesn’t then it is a bug in our code. Going forward we could:

  • Just use the same simple method on the public surface and not compile out
  • Use Debug.Assert()
  • Use something that follows the simple method pattern but is different so that it can be compiled out

We chose to use the same pattern but modified to allow compiling out. The options here are:

  • Same class, different method overloads. E.g. Check.NotNull(bar);
  • Same class, different method names. E.g. Check.DebugNotNull(bar);
  • Different class, same method names. E.g. DebugCheck.NotNull(bar);

We opted for the different class approach, but there was not strong consensus.

Last edited Dec 14, 2012 at 5:07 PM by ajcvickers, version 2

Comments

No comments yet.