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

HasOne/HasMany used with single string can be confusing #9171

Closed
ajcvickers opened this issue Jul 13, 2017 · 9 comments
Closed

HasOne/HasMany used with single string can be confusing #9171

ajcvickers opened this issue Jul 13, 2017 · 9 comments
Assignees
Labels
breaking-change closed-fixed The issue has been fixed and is/will be included in the release indicated by the issue milestone. type-enhancement
Milestone

Comments

@ajcvickers
Copy link
Member

See discussion on #6674

If a relationship is configured like this:

modelBuilder.Entity<Samurai>().HasOne("Entrance").WithOne();

it looks like it should be using the navigation property "Entrance", which may be private. But really it is the related entity type name:

 public virtual ReferenceNavigationBuilder HasOne(
            [NotNull] string relatedTypeName,
            [CanBeNull] string navigationName = null)

The correct way to configure it is something like:

modelBuilder.Entity<Samurai>().HasOne(typeof(Entrance), "Entrance").WithOne();
@ajcvickers
Copy link
Member Author

Comment from @divega:

I agree that is confusing 😞 The fact that the type isn't being specified as a generic argument but is still needed is very easy to miss. I wonder if this is the only API in which this happens.

If we believe it is worth addressing it, something like this may help:

  1. Obsolete this version of HasOne that doesn't take a generic argument
  2. Create a new API called HasOneOfType for this case. It would look like this:
modelBuilder.Entity<Samurai>().HasOneOfType("Entrance", navigationName: "Entrance")
    .WithOne();

I think it helps because it reads like the (first) argument is the type.

This looks complicated though:

modelBuilder.Entity<Samurai>().HasOneOfType(typeof(Entrance), navigationName: "Entrance")
    .WithOne();

Maybe we do it only for the one that works with strings?

Perhaps there is a better solution.

@ajcvickers
Copy link
Member Author

@divega A few of things I have been thinking about:

  • If the navigation property exists (even if it is private) that we don't need the type name because it can be obtained from the navigation property. The only reason to pass both as arguments is when configuring a shadow state nav property, currently used only for the model snapshot.
  • Having a generic overload for the type would be nice, even if the type is not needed, because it allows the generic to flow through for lambda expressions later later in the chain. I wonder if there is a reason we didn't add it here.
  • I wonder if we could just change it (probably in 3.0) to really have an single string overload just for the navigation name. This might be okay, because the times when you really want to only pass the single string type name seem rare to me--relationship with no navigation property where you don't want to use typeof() for some reason. Also, in this case there isn't a navigation property that matches, so after the change if there was code using this it could throw. It's hackish, but it could even warn and then drop back to using the name as a type if the navigation was not found--but not sure we even need to go that far.

@smitpatel
Copy link
Member

smitpatel commented Jul 13, 2017

But we need type always to find out what is related entity type. i.e. where to search that navigation.

@ajcvickers
Copy link
Member Author

@smitpatel In the example, Samurai has a property called Entrance, which is of type Entrance. So the type of the navigation property tells us the type of the related entity.

@ajcvickers
Copy link
Member Author

Filed #9191 for the generic overload. For the rest, putting in 3.0 as a possible breaking change that we could make at that time. Note that the type name has to be a full entity type name, and hence the code above while passing in OnModelCreating will fail at model validation time, which may make it easier to change the behavior without breaking any real code, at least once re-compiled.

@ajcvickers ajcvickers self-assigned this May 17, 2018
@ajcvickers ajcvickers modified the milestones: Breaking Changes, 3.0.0 May 17, 2018
@AndriySvyryd AndriySvyryd changed the title WithOne used with single string can be confusing HasOne/HasMany used with single string can be confusing Jun 15, 2018
@AndriySvyryd
Copy link
Member

If the single string overload is changed to mean navigation it needs to retain old behavior when called on a shadow entity type to ensure old snapshots aren't broken.

New snapshots can be generated to always call the two parameter overload.

@ajcvickers
Copy link
Member Author

ajcvickers commented Jun 15, 2018

Maybe we need a way to determine that we are building a snapshot--e.g. by setting some flag. That way we could special case places like this.

@AndriySvyryd
Copy link
Member

We can use the ProductVersion annotation to do this only on pre 3.0.0 snapshots

@AndriySvyryd
Copy link
Member

AndriySvyryd commented Feb 1, 2019

Also replace HasForeignKey and HasPrincipalKey overloads on ReferenceReferenceBuilder and ReferenceReferenceBuilder<,> that have the same issue.
When the type is not specified then the call order would determine which entity type is the dependent - the one on which the relationship configuration began.

ajcvickers added a commit that referenced this issue Feb 24, 2019
Issue #9171

Now if these methods are called with a single string, then that string represents the navigation property name. If there is no CLR property with the given name, then an exception is thrown.

The exception is when the entity type has no CLR type, in which case the old behavior is preserved so as not to break existing model snapshots. Entity types with no CLR type are only valid in model snapshots.
@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 Feb 24, 2019
ajcvickers added a commit that referenced this issue Feb 28, 2019
Issue #9171

Now if these methods are called with a single string, then that string represents the navigation property name. If there is no CLR property with the given name, then an exception is thrown.

The exception is when the entity type has no CLR type, in which case the old behavior is preserved so as not to break existing model snapshots. Entity types with no CLR type are only valid in model snapshots.
@ajcvickers ajcvickers modified the milestones: 3.0.0, 3.0.0-preview4 Apr 15, 2019
@ajcvickers ajcvickers modified the milestones: 3.0.0-preview4, 3.0.0 Nov 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
breaking-change 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

4 participants