Skip to content

Strict keys #12187

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

Merged
merged 4 commits into from
Jul 16, 2019
Merged

Strict keys #12187

merged 4 commits into from
Jul 16, 2019

Conversation

SteveSandersonMS
Copy link
Member

@SteveSandersonMS SteveSandersonMS commented Jul 15, 2019

Implements #11908

As suggested by @Andrzej-W, this changes the default behavior for @key so that during diffing, if we encounter any duplicate values then we throw and fail. This makes it more likely for developers to discover any bugs they might have with unexpected duplicates, and avoids the risk that element/component preservation might behave differently than the developer expects in those cases.

This PR also adds the option to have any given key treated as "loose", meaning that it's allowed to be null or a duplicate of another key value. Both of these scenarios are handled by simply ignoring that key, including any other instances where it is duplicated. That's more or less the same as the previous behavior, except that previously we didn't allow null at all.

Later, I expect the syntax for loose mode to be @key:loose (or @key:loose="true"), but since that's not yet implemented in the compiler, loose mode currently can only be used from manual rendertreebuilder logic, hence implementing part of the E2E test that way.

Update: as per discussion below, I've removed the loose mode, because (1) it's hard to say what the circumstances would be where you would actually want to use it, and (2) if you really do, then it can be achieved in user code instead. For example, if you have a list where some of them items are already saved to the DB and hence have a PK value, whereas others are still being created and hence don't have a PK value, then you probably still do want predictable preservation behavior for the uncommitted items, so you would achieve that by giving them a temporary GUID or something until they are committed. In case someone doesn't want to supply any key at all for a certain item, it's now allowed just to pass null, which treats that item as unkeyed.

@SteveSandersonMS SteveSandersonMS added the area-blazor Includes: Blazor, Razor Components label Jul 15, 2019
@SteveSandersonMS SteveSandersonMS added this to the 3.0.0-preview8 milestone Jul 15, 2019
@SteveSandersonMS
Copy link
Member Author

Actually, having done this, I’m now reconsidering the idea of there being a loose mode option. It may be better just to only have the strict mode.

The reason is that you could make something functionally equivalent to loose mode in user code anyway (e.g. a method that returns the key if it’s unique within a set, or a guid/null if it’s not). It’s an edge case and probably doesn’t warrant being a built-in flag.

@javiercn
Copy link
Member

The reason is that you could make something functionally equivalent to loose mode in user code anyway (e.g. a method that returns the key if it’s unique within a set, or a guid/null if it’s not). It’s an edge case and probably doesn’t warrant being a built-in flag.

I think I agree with your reasoning. If the key is null though, wouldn't that throw?

@SteveSandersonMS
Copy link
Member Author

If the key is null though, wouldn't that throw?

Yes, I didn't mention, but part of the intention for the "no loose mode" plan was to tolerate nulls, and treat them as meaning "don't have a key on this one".

@SteveSandersonMS SteveSandersonMS force-pushed the stevesa/key-strict branch 2 times, most recently from 20b407f to 0e72539 Compare July 15, 2019 15:28
@@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.Components.RenderTree
/// <summary>
/// Describes the type of a <see cref="RenderTreeFrame"/>.
/// </summary>
public enum RenderTreeFrameType: int
public enum RenderTreeFrameType: short
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly speaking this change isn't required in this PR, but I'd like to include it because it's a good thing and means we have less to reason about in the future if we want to make use of the memory space this frees up.

@SteveSandersonMS
Copy link
Member Author

OK, I've updated this PR now so that the only mode is strict mode.

@SteveSandersonMS SteveSandersonMS changed the title Strict keys, with option for loose mode Strict keys Jul 15, 2019
@javiercn
Copy link
Member

Do we still avoid throwing on null/default keys?

@javiercn
Copy link
Member

Do we still avoid throwing on null/default keys?

Couldn't this hide implementation errors?

Copy link
Member

@javiercn javiercn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changes look good. I'm wondering if we should check for duplicate nulls/defaults as that might indicate a developer error is lurking below. If you have a key, you should have a good function for it that produces unique values.

After all, you are likely not using the key with object that have a value identity but with objects that have some sense of reference identity.

Do we use GetHashCode() by default, should we be concerned about collisions there?
Should this just be guidance to use key when you have a field that has some sort of referential identity?

@SteveSandersonMS
Copy link
Member Author

If you have a key, you should have a good function for it that produces unique values.

You usually would do, but there are cases where you don't. For example you could have a collection that contains some items that are already saved to the DB and hence have IDs, but other items that are still being created and hence don't have IDs.

In cases like this developers may choose to assign temporary IDs to the unsaved items, or they may not. If they don't, it's pretty awkward if we force them to write if/else blocks just to vary whether they are specifying a @key directive attribute. It's much friendlier just to allow null.

Couldn't this hide implementation errors?

Maybe, but it's a tradeoff.

Do we use GetHashCode() by default, should we be concerned about collisions there?

No, we don't use GetHashCode. We're just using Dictionary<T,V>'s key-equality comparer.

Should this just be guidance to use key when you have a field that has some sort of referential identity?

If by "referential" you mean object reference identity, then I don't think so - it would be legit to use it with successive DB queries that return different actual object instances but retain ID values.

The guidance is to use it when you have something that's meaningful to use as a key, which could be object identity, or it could be something like a PK value from a DB query result.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-blazor Includes: Blazor, Razor Components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants