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

Generate All Possible Values Other Than PKs When Entities Are Tracked From Detached State #22149

Conversation

TheFanatr
Copy link
Contributor

@TheFanatr TheFanatr commented Aug 20, 2020

Makes it so a value for __id is created on entities which were in a Detached state but are then tracked in a new state on an EFCore.Cosmos-backed context; DbContext methods used to transition detached entities into another state would not otherwise work, other than Add and AddAsync. This is discussed in #15289.

The initial fix for this was local to EFCore.Cosmos, but was then it was modified and written into EFCore, so that now all non-PK values will be created when detached entities are tracked in another state.

Fixes #15289

…ed value if it is incorrectly empty when entity tracking state changes occur.

This is done via the creation and provisioning of a custom state manager (IStateManager) service containing the logic.
@AndriySvyryd
Copy link
Member

StateManager is not intended to be overridden by providers. Instead you'd need to modify the core implementation to call the value generator on non-pk properties that don't have a value when attaching.

@ajcvickers Might provide some thoughts

@ajcvickers
Copy link
Member

@AndriySvyryd

Instead you'd need to modify the core implementation to call the value generator on non-pk properties that don't have a value when attaching.

Do you think this is something we can start doing always?

@AndriySvyryd
Copy link
Member

Do you think this is something we can start doing always?

Yes, it might be a minor breaking change, but I can't come up with a scenario where this would be blocking

@TheFanatr
Copy link
Contributor Author

TheFanatr commented Aug 21, 2020

StateManager is not intended to be overridden by providers. Instead you'd need to modify the core implementation to call the value generator on non-pk properties that don't have a value when attaching.

Is this behavior of populating the __id property not specific to the Cosmos DB provider? I realize this can be generalized, but would it not be easier to simply handle this scenario, as it is preventing context.Attach from operating correctly in this provider specifically?

@TheFanatr
Copy link
Contributor Author

Will start working on this anyways.

@AndriySvyryd
Copy link
Member

Is this behavior of populating the __id property not specific to the Cosmos DB provider? I realize this can be generalized, but would it not be easier to simply handle this scenario, as it is preventing context.Attach from operating correctly in this provider specifically?

While it's most evident in Cosmos it would also be used for data seeding across all providers.

@TheFanatr
Copy link
Contributor Author

@AndriySvyryd

call the value generator on non-pk properties that don't have a value when attaching

The logic needs to also happen on update for the Cosmos DB provider, because otherwise it will also cause entity tracking to throw. Also, how to I restrict ValueGenerationManager to only non-PKs without just copying out all the logic and changing it. I came up with a stripped-down version, but I'm sure it misses some edge cases.

foreach (var property in entry.EntityType.GetProperties().Where(property => !property.IsPrimaryKey() && entry.GetCurrentValue(property) is null))
{
    var valueGenerator = property.GetValueGeneratorFactory()(property, entry.EntityType);
    entry.SetStoreGeneratedValue(property, valueGenerator.Next(entry.ToEntityEntry()));
}

Can I just change IValueGenerationManager.Generate to include bool includePKs = true which just conditionally includes or excludes PKs in the value generation process based on that flag being set, then just call the method from the state manager on every change to Unchanged or Modified?

@AndriySvyryd
Copy link
Member

Can I just change IValueGenerationManager.Generate to include bool includePKs = true which just conditionally includes or excludes PKs in the value generation process based on that flag being set,

Yes

then just call the method from the state manager on every change to Unchanged or Modified?

Only if changing from Detached

@AndriySvyryd AndriySvyryd self-assigned this Aug 21, 2020
…nerateAsync methods, add logic to StateManager to use ValueGenerationManager to generate values if necessary for non-PK properties on entities which change from the Detached state to the Modified or Unchanged state, extend test to include checking that the Update method also works on the context, create and use test-specific context IdentifierShadowValuePresenceTestContext in test, remove definition and use of CosmosStateManager, and rename test database to IdentifierShadowValuePresenceTest.
@TheFanatr
Copy link
Contributor Author

I added bool includePKs = true to GenerateAsync on IValueGenerationManager as well, just for consistency's sake.

…tity deletion via normal use of DbContext Remove method works, make other minor changes to test, and rename test to have more accurate declared success condition.
@TheFanatr
Copy link
Contributor Author

Had to make value generation occur even when entities are newly tracked in the Deleted state. Is this an issue?

@TheFanatr TheFanatr changed the title [Cosmos] Populate __id Shadow Property on Entity Attach Generate All Possible Values Other Than PKs When Entities Are Tracked From Detached State Aug 22, 2020
@TheFanatr
Copy link
Contributor Author

TheFanatr commented Aug 22, 2020

Do I need to check that newState is not Added here? I do now because EFCore.Cosmos would create a value for __id in this case without this fix, but I'm unsure as to if this exclusion makes sense or is intuitive in terms of behavior for the rest of EF.

@AndriySvyryd
Copy link
Member

Do I need to check that newState is not Added here?

You should do it here instead

@TheFanatr
Copy link
Contributor Author

You should do it here instead

What do you mean? I was just asking whether or not it matters that generation does not occur in the StateChanging method on StateManager when newState is Added. Also what does adding mean in the context of the line you were referencing? Is it meant as in newly attached in any state or as in it will be attached in the Unchanged state after save? The following is what I have come up with based on my understanding.

if (adding || oldState is EntityState.Detached)
{
    StateManager.ValueGenerationManager.Generate(this, includePKs: adding);
}

@TheFanatr
Copy link
Contributor Author

This seems to work.

…ure information semantics than available with Assert True where reasonable, remove value creation from StateManager StateChanging and into core InternalEntityEntry logic within SetEntityState and SetEntityStateAsync methods.
@TheFanatr
Copy link
Contributor Author

I have added a baseline test to make sure that the Add behavior of creating a value for the PK when it is a nullable value with a generator (such as Guid?) still works as intended.

@AndriySvyryd
Copy link
Member

@TheFanatr You understood me correctly

@TheFanatr
Copy link
Contributor Author

Is the adding check redundant?

@AndriySvyryd
Copy link
Member

Is the adding check redundant?

No, we've been generating values when changing to Added from states other than Detached, better not to change this behavior

@AndriySvyryd AndriySvyryd merged commit 04575b0 into dotnet:release/5.0 Aug 24, 2020
@AndriySvyryd
Copy link
Member

Thanks for your contribution!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Cosmos: id not populated on Attach
3 participants