Design Meeting Notes - February 20, 2013

Mapping to private fields

This discussion was triggered by a potential contribution from  Unai Zorrilla Castro. We took advantage of Unai’s visit for the MVP summit to discuss the design with him.

Goals

Domain classes often need to restrict what client code can do. For example:

  • Changes to a collection should always go through a method that keeps aggregate root consistency
  • Properties on a value object should be immutable
  • Objects can only be constructed in a consistent state

EF can currently map to properties with private setters to achieve this, but it is cleaner if EF doesn’t require the setters at all.

In addition, current EF POCO requirements go against some common practices. For example

  • Read-only properties
  • Collections exposed as a read only collections with methods for modifying the collections

Allowing EF to map to private fields would enable these scenarios and also form the first part of more generally flexible object to conceptual model mapping—aka o/c mapping.

Approach

The plan is to tackle this in several phases:

  1. Mapping only by convention and only in Code First. In this case properties would still need to exist but would not have setters.
  2. Fluent APIs to configure which properties should use fields. This enables mapping to fields when no property is present.
  3. Support for collections exposed as IEnumerable<T>
  4. Support for Database First and Model First (not addressed here)

Initial ideas

Update Code First and O-C mapping recognize read-only properties with backing fields

  • Field names matched by convention using the following patterns where the first match in the given list (for a property called MyName) is used:
    • myName
    • _myName
    • _MyName
    • m_myName
    • m_MyName
  • The type of the field must also be compatible—that is::
    • Scalars and reference navigation properties must have an exact type match
      • We could potentially allow anything assignable, but this increases the chances of false positives and may require additional changes to the materializer, etc. for type conversions.
    • Collection navigation properties must be a non-read-only ICollection<T>
  • Only instance fields should be considered
  • Note that scalar properties, navigation properties, complex properties, and properties of complex types should all be considered

Notes:

  • Code First will use these rules to detect and map read-only properties. This information could be flowed through the model as an annotation and then used for o/c mapping, or o/c mapping could re-apply the rules.
  • Once a backing field has been identified or configured (assuming APIs exists to do this configuration) then the field will be used by all access from EF, even though the getter/setter for the property may still be present.
  • Exposing collections as IEnumerable<T> should be allowed, but this doesn’t have to be part of the first pull request
  • Classes that use this pattern will not meet the requirements for change tracking proxies
  • Lazy loading properties should work as long as a public or protected virtual getter is present. The proxy code will need to be updated to use the field to obtain the collection to which entities will be added.

Examples

Read-only properties:

public class Customer
{
    public int Id { get; set; }

    Address _Address;
    public Address Address
    {
        get { return _Address; }
    }

    public void ChangeAddress(Address newAddress)
    {
        _Address = newAddress;
    }

Read-only collections:

public class Order
{
    private List<OrderLine> _Lines = new List<OrderLine>();

    public ICollection<OrderLine> Lines
    {
        get { return new ReadOnlyCollection<OrderLine>(_Lines); }
    }

    public void AddNewLine(OrderLine line)
    {
        if (line != null)
        {
            //aggregate consistency logic here!
            _Lines.Add(line);
        }
    }

Breaking changes implication

It is inevitable that introducing this convention will result in Code First detecting and mapping some properties that it did not previously map. This is mitigated by only detecting properties that have a matching backing field such that most computed properties will not be detected. However, the question remains as to whether or not this will be an acceptable change and can be on by default, or whether it is something that needs to be switched on explicitly.

Note that this is not a new issue—it is analogous to Code First starting to map enums in EF5.

The initial decision here is to allow the convention to happen by default because:

  • This is the best experience for using the feature going forward
  • We haven’t received any feedback that it was a problem for people in the enums case
  • Breaking changes to a model should are usually quickly detected
  • It is easy to ignore any properties that are now erroneously detected
  • It will be possible to revert to the current behavior by specifying a DbModelBuilderVersion
  • If it seems to be a problem in pre-release versions of EF we can change the behavior before going RTM

Note that while the model builder version will control this behavior it probably doesn’t need to be handled by a convention—see handling of this for enums.

Fluent API ideas

The notes above cover mapping read-only properties by convention. API is required for:

  • Mapping fields that do not have associated properties
  • Mapping to fields where the field name doesn’t match the property name by convention
  • Mapping to fields even when a property with a getter and a setter exists

The entry point for the API could be the existing property fluent. For properties that have a getter (and possible a setter) but for which a backing field should be used by EF for access could be configured like so:

modelBuilder
    .Entity<Blog>()
    .Property(b => b.Title)
    .UseBackingField("_theTitle");

Note that currently “Property” is only used for scalar properties. We would need to either relax this, do better overload resolution, or add different APIs for navigation properties.

This API should also support lambda expressions for the field name, although this is less useful than is normally the case since fields will typically be private. Still, it can be used with the same patterns that are used for mapping to private properties.

This API can also be used to tell Code First to map a property using the backing field even if that property has a getter and a setter. In such a case the field name can be specified as above, or if the field name matches by convention then UseBackingField can be called with no parameter. For example:

modelBuilder
    .Entity<Blog>()
    .Property(b => b.Title)
    .UseBackingField();

For cases where there is no property the name of the property should be passed as a string. For example:

modelBuilder
    .Entity<Blog>()
    .Property<string>("Title")
    .UseBackingField("_theTitle");

Note that in this case the generic argument specifies the type of the property. Possibly this could be inferred from the type of the field, but that would impact the fluents that can be chained from this method.

This same API could be used for creating shadow state properties simply by not specifying a backing field. For example:

modelBuilder
    .Entity<Blog>()
    .Property<int>(b => b.Id);

Additional notes:

  • Multiple properties should not be allowed to map to the same field. There doesn’t seem like any compelling scenarios for this that can’t be handled by having one of the properties un-mapped, and it would be easy to do it by mistake (e.g. copy/paste error) if it is allowed.
  • Lightweight conventions should support mapping to fields with different names by convention.

Unused resources

We currently have a couple of dozen un-used string resources in the EntityFramework assembly. What are the chances of breaking anything if they are removed? For example:"

  • Use by the designer?
    • The designer will not grab resources out of EF.dll
  • Use by templates?
    • Only the DbContext templates might possibly be using strings out of EF.dll
    • Currently these come from S.D.E.dll—it is acceptable to keep doing this for now, with possible move in the future if necessary
  • Use by external code?
    • External code should not be grabbing EF resources so we will not lock on this for removing them

Conclusion: go ahead with removal.

Last edited Feb 21, 2013 at 2:03 AM by ajcvickers, version 2

Comments

samlebon23 Apr 19 at 2:49 PM 
Hi Unai,
Will this important features be implemented in the near future? EF 7.0 ?

Thanx.

ggondim Nov 25, 2013 at 7:38 AM 
It is REALLY important. Microsoft didn`t preview that it would be a problem. For example, the EDM model is mapped also for the OData Service Reference, in the new WebAPI 2.0. When importing the Service Reference to another project, the service only can consume the mapped properties and when posting readonly properties they are not deserialized in the EntitySetController, because they haven`t a set method.

Discofunk Jul 14, 2013 at 7:35 AM 
Great to see that these important concepts are being discussed. However, it doesn't look like they are part of the EF 6 road map which is a little disheartening.