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

Hierarchical IDs? #82

Closed
mikechristiansenvae opened this issue Sep 28, 2022 · 3 comments · Fixed by #117
Closed

Hierarchical IDs? #82

mikechristiansenvae opened this issue Sep 28, 2022 · 3 comments · Fixed by #117

Comments

@mikechristiansenvae
Copy link

In one of my applications, I have a set of hierarchical IDs.

Take, for example, if I wanted to have a way to uniquely identify an aisle in a store. Without StronglyTypedId, I might do this:

public readonly record struct StoreId(int Id)
{
    public string ToString() => Id.ToString();
}
public readonly record struct SectionId(StoreId Store, int Id)
{
    public int StoreId => Store.Id;
    public string ToString() => $"{StoreId}/{Id}";
}
public readonly record struct AisleId(SectionId Section, int Id)
{
    public int SectionId => Section.Id;
    public int StoreId => Section.StoreId;
    public string ToString() => $"{StoreId}/{SectionId}/{Id}";
}

Each level of the hierarchy provides a different context. A SectionId with an integer value of 1, in StoreId 1 is not the same as a SectionId with an integer value of 1, in StoreId 2

It would be nice if I could do something similar with StronglyTypedId.

@NxSoftware
Copy link
Contributor

You can already do this, I'm doing something similar in one of my private projects.

Using [StronglyTypedId(backingType: StronglyTypedBackingType.String)] would allow you to store the Value as the slash delimited path from your ToString methods and there's nothing stopping you adding additional properties to track the StoreId and SectionId.

The only drawback would be you'd get a constructor that would allow you to create an instance with a raw string value however my PR #79 would resolve this by being able to disable the generation of the constructor.

@andrewlock
Copy link
Owner

Sorry for the delay with this 😳. I'm currently working on a big redesign of the library in this PR:

The main idea is to make the library much more maintainable while also giving people a mechanism to customise the generated IDs as much as they like. This will make it easy to make changes like this 🙂

@mikechristiansenvae
Copy link
Author

For what its worth, I ended up implementing this in my project, but I also needed to have some limited inheritance.

To reuse the example I gave above, suppose I had

public readonly record struct StoreId(int Id)
{
    public string ToString() => Id.ToString();
}
public readonly record struct GroceryStoreId(int Id) : StoreId(Id) // Uh oh!  Can't inherit from a struct!!
{
}

So, to handle inheritance, I needed to use a reference type. But, I did not want to incur the cost of instantiation each time. Additionally, I would want to be able to use ReferenceEquals for speed.

Looking back, I knew that XNamespace and XName atomized their values. So, I lifted XHashtable<TValue> from the dotnet repo (MIT license). I modified XHashtable<TValue> to support keys (and renaming it to IdAtomizer<TKey, TValue>.

Then, I have some abstract base classes:

  • BaseId<TKey>, with a protected static method (named Get) to get or create an instance with a given ID (int, string, whatever)
  • BaseId<TParent, TKey>, with a protected static method (named Get) to get or create an instance with a given parent and a given ID (int, string, whatever)
  • IntegerBaseId (inherits BaseId<int>) and IntegerBaseId<TParent> (inherits BaseId<TParent, int>), have a protected static method (named GetNext) to create the "next" ID (auto increment). It also retains the Get method from its base class, to get an arbitrary instance.

Then, derived classes would simply need to add a public static Get method (and in the case of integer keys, GUIDs, or other key types that can be sequentially or randomly generated, GetNext) that calls the protected Get method. (This two step process is required so that the constructor on the derived classes can remain private; a creation delegate is passed). For IDs that are parents, helper (instance) methods are created to allow you to get the next (or specific) child ID.

It works well for me. Other people might like the same approach. If you're interested, I can add the IdAtomizer and the base classes to an MR for your review.

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 a pull request may close this issue.

3 participants