Skip to content
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

Enable model-level entity properties to be associated to a backing field but no property getter or setter #4357

Closed
divega opened this issue Jan 21, 2016 · 11 comments
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-enhancement
Milestone

Comments

@divega
Copy link
Contributor

divega commented Jan 21, 2016

Currently the string version of Property<T>() can only be used to create a shadow state property. This issue is about enabling declaring model-level properties on CLR types that are backed by a field but don't have a property getter or setter. E.g. assuming a fluent API for setting the "BackingField" annotation, we could enable code like this:

ModelBuilder.Entity<Person>().Property<DateTimeOffset>("BirthDate").UseBackingField("_birthdate");
@divega
Copy link
Contributor Author

divega commented Jan 21, 2016

For triage: I am almost sure we said this had to be in the backlog but leaving the milestone empty just in case.

@divega divega changed the title Enable entity properties to be associated to a backing field but no property getter or setter Enable model-level entity properties to be associated to a backing field but no property getter or setter Jan 21, 2016
@rowanmiller rowanmiller added this to the Backlog milestone Feb 1, 2016
@ajcvickers ajcvickers self-assigned this Jul 28, 2016
@ajcvickers
Copy link
Contributor

@divega @rowanmiller @anpete @AndriySvyryd

Here is a proposal based on the discussion we had yesterday. Thoughts?

.Property(e => e.Id).UseField("_key"); 
.Property("Id").UseField("_key");

If the property is a CLR property, then this just configures the backing field to use for that property--i.e. it is just fluent API for what we already have.

If the property is not a CLR property, then this makes the property always use the field for storage. So it is no longer a shadow property and becomes a mapping to the field.

.Property("_key").UseField("_key");
.Property("_key").UseField();

These both do the same thing--the property in our metadata is now named the same as the CLR field. This is functionally the same as the field mapping above.

Related to this is #4855 which is about whether to use the property or the backing field directly when accessing properties. Proposal is to have three modes:

  • PreferProperty (Everywhere uses the property if possible)
  • PreferFieldForQuery (Materialization prefers field; everywhere else prefers property)
  • PreferField (Everywhere uses the field if possible)

If we are preferring properties, then we will use the property getter/setter if available, otherwise fall back to using the field. Likewise, if we are preferring field, then we will use the field unless the field is not specified or cannot be found, in which case we fall back to using the property.

The default is PreferFieldForQuery, which matches our current behavior.

This mode can be set at the Model, Entity, or Property level:

modelBuilder.HasPropertyAccessMode(PreferProperty);
modelBuilder
    .Entity<Foo>()
    .HasPropertyAccessMode(PreferProperty);
modelBuilder
    .Entity<Foo>()
    .Property(e => e.Id)
    .HasPropertyAccessMode(PreferProperty);

So if you map all your fields and use PreferField on the model, then we will never access your properties and you get complete mapping to backing fields.

Very open to suggestions for naming.

@anpete
Copy link
Contributor

anpete commented Jul 28, 2016

Looks good. Question:

When there is no CLR property "_key", will

.Property("_key");

add a field-backed property if a matching field is found?

Perhaps HasPropertyAccessMode -> UsePropertyAccessMode

@ajcvickers
Copy link
Contributor

@anpete Depends whether or not we think that any significant number of people are currently doing this and have working apps with shadow state properties. Safest thing is to not do it. Best experience assuming we think we can make the break is to do it.

@anpete
Copy link
Contributor

anpete commented Jul 28, 2016

@ajcvickers Could be a feature flag, too.

@ilmax
Copy link
Contributor

ilmax commented Jul 28, 2016

Can we have a shortcut syntax that infers, based on a convention, the name of the backingField to avoid a bunch of magic strings e.g.

.Property(e => e.Id).UseField();

infers to use a backing field called _id

In this case the convention uses by default a field which is named _{camelCasedPropertyName}. It would be even nicer if we can replace this convention with a custom one.

@ajcvickers
Copy link
Contributor

@ilmax EF Core already does that.

@ilmax
Copy link
Contributor

ilmax commented Jul 28, 2016

@ajcvickers nice 😄, sorry for asking

@divega
Copy link
Contributor Author

divega commented Jul 29, 2016

At first glance the proposal looks good and it seems that having UseField() and HasPropertyAccessMode() defined like this should be fine because they are different (one is an explicit configuration API and the other is to select default behaviors) however there seems to be some ambiguity or overlap between them, in particular when HasPropertyAccessMode() is applied to property configuration. E.g.:

  • Being able to write something like this seems pointless:

    modelBuilder
    .Entity<Foo>()
    .Property(e => e.Id)
    .UseField("_id")
    .HasPropertyAccessMode(PropertyAccessMode.PreferProperty);
  • There doesn't seem to be a way to explicitly indicate that a field should not be used, e.g. something like UseField(false) on property configuration.

I have a few ideas of tweaks we could try on this proposal but I think it would be more productive to chat in person.

Orthogonal to that:

  • Assuming PropertyAccessMode is an enum, we could name its members just Property, Field, etc. then we could make the second API DefaultPropertyAccessMode()
  • All the behaviors we have talked about seem to be centered only on how property values are set. I wonder if how EF reads property values from objects (e.g. during fixup and on SaveChanges()) is interesting enough to add granular control for it.

@rowanmiller
Copy link
Contributor

Notes from triage

// Configure a field (will be used during materialization but not fix-up etc.)
modelBuilder
  .Entity<Foo>()
  .Property(e => e.Id)
  .HasField("_id");

// Configure a field and enforce it is always used
modelBuilder
  .Entity<Foo>()
  .Property(e => e.Id)
  .HasField("_id")
  .UsePropertyAccessMode(PropertyAccessMode.Field);

// Skip using field even if it was found by convention
modelBuilder
  .Entity<Foo>()
  .Property(e => e.Id)
  .UsePropertyAccessMode(PropertyAccessMode.Property);

// Enforce that a field was found and should be used
modelBuilder
  .Entity<Foo>()
  .Property(e => e.Id)
  .UsePropertyAccessMode(PropertyAccessMode.Field);

// Stop using fields even where they were found by convention
modelBuilder.UsePropertyAccessMode(PropertyAccessMode.Property)

// Enforce that all access goes thru fields (except shadow properties)
modelBuilder.UsePropertyAccessMode(PropertyAccessMode.Field)

PropertyAccessMode.Field
PropertyAccessMode.FieldDuringConstruction
PropertyAccessMode.Property

@rowanmiller
Copy link
Contributor

Also note that an atribute would allow you to use nameof to set the field

@rowanmiller rowanmiller modified the milestones: 1.1.0, Backlog Jul 29, 2016
ajcvickers added a commit that referenced this issue Aug 8, 2016
Issues #4357, #4855

This change adds fluent and lower-level APIs to set backing fields or properties with no fields as well as conventions for using the fields and a mechanism to provide control over when the property is used and when the field is used.

Also see #6266, #6267, and #6268.
ajcvickers added a commit that referenced this issue Aug 8, 2016
Issues #4357, #4855

This change adds fluent and lower-level APIs to set backing fields or properties with no fields as well as conventions for using the fields and a mechanism to provide control over when the property is used and when the field is used.

Also see #6266, #6267, and #6268.
ajcvickers added a commit that referenced this issue Aug 8, 2016
Issues #4357, #4855

This change adds fluent and lower-level APIs to set backing fields or properties with no fields as well as conventions for using the fields and a mechanism to provide control over when the property is used and when the field is used.

Also see #6266, #6267, and #6268.
ajcvickers added a commit that referenced this issue Aug 10, 2016
Issues #4357, #4855

This change adds fluent and lower-level APIs to set backing fields or properties with no fields as well as conventions for using the fields and a mechanism to provide control over when the property is used and when the field is used.

Also see #6266, #6267, and #6268.
@ajcvickers ajcvickers added the closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. label Aug 10, 2016
@ajcvickers ajcvickers modified the milestones: 1.1.0-preview1, 1.1.0 Oct 15, 2022
@ajcvickers ajcvickers removed their assignment Sep 1, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-enhancement
Projects
None yet
Development

No branches or pull requests

5 participants