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

Added the concept of Primary Key Handlers #149

Merged
merged 25 commits into from
Jul 6, 2021

Conversation

slewis74
Copy link
Contributor

@slewis74 slewis74 commented Jun 14, 2021

This PR is based on the learnings from #146 and the updates being proposed in #145 for Identity primary key support.

It adds the concept of PrimaryKeyHandlers, which in many ways are similar to having TypeHandlers, but specific to the use cases required of primary keys, e.g. INSERTs and DELETEs operations.

Reviewing is probably easiest by commit. The first 2 build out the functionality and the 3rd updates the Customer class in the integration tests to use a CustomerId primary key field.


namespace Nevermore.IntegrationTests.Model
{
class StringTinyTypeIdTypeHandler<T> : ITypeHandler where T : TinyType<string>
Copy link
Contributor

Choose a reason for hiding this comment

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

I would avoid referencing TinyType name anywhere in Nevermore because this might cause quite a bit of confusion. What about CustomIdType instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yep 👍


namespace Nevermore.IntegrationTests.Model
{
public class TinyType<T>
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as above. e.g. CustomIdType

/// <typeparam name="TKey">The type of the Id</typeparam>
/// <param name="id">The <c>Id</c> of the document to find.</param>
/// <returns>The document, or <c>null</c> if the document is not found.</returns>
[Pure] TDocument Load<TDocument, TKey>(TKey id) where TDocument : class;
Copy link
Contributor

Choose a reason for hiding this comment

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

We need generic members for most of the queries, I can imagine they will be added once we have a solution to the main problem?

/// </summary>
protected string IdPrefix
protected IPrimaryKeyHandler? PrimaryKeyHandler
Copy link
Contributor

Choose a reason for hiding this comment

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

It doesn't feel right to have the primary key handler specified in the mapping. Do we really need it here?

{
class GuidPrimaryKeyHandler : PrimitivePrimaryKeyHandler<Guid>
{
public override object FormatKey(string tableName, int key)
Copy link
Contributor

Choose a reason for hiding this comment

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

This feels wrong and only works because we actually don't call this method. If we did this cast would fail

return (TKey) AllocateIdUsingHandler(mapping, primitivePrimaryKeyHandler);

Copy link
Contributor

Choose a reason for hiding this comment

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

What if FormatKey was renamed to GetKey(tableName). StringPrimaryKeyHandler could us keyAllocator internally to get a unique id.

/// <param name="idPrefix">The function to call back to get the prefix.</param>
public IIdColumnMappingBuilder Prefix(Func<string, string> idPrefix)
{
if (!(PrimaryKeyHandler is null) && Direction == ColumnDirection.FromDatabase && PrimaryKeyHandler is IIdentityPrimaryKeyHandler)
Copy link
Contributor

Choose a reason for hiding this comment

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

There is a bit of type checking in a few places, I can imagine you tried to remove it but that would break too much of the existing code?

{
public override object FormatKey(string tableName, int key)
{
return key;
Copy link
Contributor

Choose a reason for hiding this comment

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

Same as Guid

public Type Type => typeof(T);

[return: NotNullIfNotNull("id")]
public virtual object? GetPrimitiveValue(object? id)
Copy link
Contributor

Choose a reason for hiding this comment

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

GetPrimitiveValue convert to ConvertToPrimitiveValue ?

@pawelpabich
Copy link
Contributor

I think we are going in the direction but some abstractions don't feel fully formed. I will try to experiment with a few approaches tomorrow.

@@ -540,20 +577,23 @@ protected async Task<TResult[]> ReadResultsAsync<TResult>(PreparedCommand prepar
{
var mapping = configuration.DocumentMaps.Resolve(typeof(TDocument));

if (mapping.IdColumn is null)
Copy link
Contributor

Choose a reason for hiding this comment

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

We could mark IdColumn as not nullable if owe passed it to DocumentMap via the constructor. I can imagine this will break gazillion things in the server?

IdColumn = builtIdColumn,


public void Delete<TDocument>(int id, DeleteOptions options = null) where TDocument : class
=> Delete<TDocument>((object) id, options);
=> Delete<TDocument, int>(id, options);
Copy link
Contributor

Choose a reason for hiding this comment

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

Nice :)

return mapping.IdColumn.PrimaryKeyHandler.GetNextKey(keyAllocator, mapping.TableName);
}

public string AllocateId(string tableName, string idPrefix)
Copy link
Contributor

Choose a reason for hiding this comment

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

I was wondering if we could get rid of this method and then found this
image

Copy link
Contributor

Choose a reason for hiding this comment

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

What do you think about marking this method as obsolete?

/// <param name="keyAllocator">The key allocator to use getting the next id from.</param>
/// <param name="tableName">The table name the key is required for.</param>
/// <returns>The next key, as the type that matches the model object's Id property type. ConvertToPrimitiveValue should be called with this value if it is to be used as a Sql parameter.</returns>
object GetNextKey(IKeyAllocator keyAllocator, string tableName);
Copy link
Contributor

Choose a reason for hiding this comment

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

This abstraction is a bit leaky. Ideally, keyAllocator and tableName would be treated as implementation details of a given primary key handler. That being said, this is not a 5 minute job so happy to keep it as a future ToDo item :)

Copy link
Contributor

@pawelpabich pawelpabich left a comment

Choose a reason for hiding this comment

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

You had to jump over a lot hurdles but we are in much better place now. There is still work to do but that's ok. Thanks!

slewis74 and others added 11 commits June 25, 2021 09:12
…yKeyHandler, it only makes sense for the IStringBasedPrimitivePrimaryKeyHandler handlers
…builders, and take config in to make sure that every document map ends up with an IdColumn that has a PrimaryKeyHandler and specifies whether it is an Identity
…eated to pass to the `KeyHandler` method for the IdColumn, to customise the id handling for a given document map
…ration code, it didn't cater for custom primary key handlers. The handlers now supply it with the information it needs.
@slewis74
Copy link
Contributor Author

slewis74 commented Jul 2, 2021

This page should be updated when this PR merges. A new page for Primary Key Handlers should also be added to the Wiki.

@slewis74 slewis74 merged commit f3c566f into ap/identity-pk-support Jul 6, 2021
@slewis74 slewis74 deleted the sl/primarykeyhandlers branch July 6, 2021 23:01
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.

None yet

2 participants