Phyros.OrganizationalUnits 1.0.6

dotnet add package Phyros.OrganizationalUnits --version 1.0.6
                    
NuGet\Install-Package Phyros.OrganizationalUnits -Version 1.0.6
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Phyros.OrganizationalUnits" Version="1.0.6" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Phyros.OrganizationalUnits" Version="1.0.6" />
                    
Directory.Packages.props
<PackageReference Include="Phyros.OrganizationalUnits" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Phyros.OrganizationalUnits --version 1.0.6
                    
#r "nuget: Phyros.OrganizationalUnits, 1.0.6"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Phyros.OrganizationalUnits@1.0.6
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Phyros.OrganizationalUnits&version=1.0.6
                    
Install as a Cake Addin
#tool nuget:?package=Phyros.OrganizationalUnits&version=1.0.6
                    
Install as a Cake Tool

Phyros.OrganizationalUnits

License: MIT Build Status NuGet

A modern, configurable C# library for representing and manipulating hierarchical organizational structures as natural keys, as a replacement for synthetic/surrogate keys that have little meaning outside of a database.

For example, let's say you have an organization with different regions and facilities; you take an order from a specific facility, and you want to know where that order came from. Instead of having a lookup with a "RegisterId" that isn't a independently meaningful value, use something like myclient.westregion.mainstreetstore.customerservice.register; so you know which client it is, which region, which store, and which register. Want to see all of the orders from a specific store? Just look for all orders with an organizational unit that starts with myclient.westregion.mainstreetstore. Or for orders taken in the West Region, just look for all orders with an organizational unit that starts with myclient.westregion. This allows you to easily query and filter orders based on their organizational context.

Targets both .NET 8 and .NET Standard 2.0.

Features

  • Parse, validate, and serialize hierarchical organizational unit strings
  • Define natural keys for organizational structures
  • Fully configurable base organizational unit alias and delimiter character
  • Instance-based or global configuration
  • Clean, testable API with XML documentation
  • Multi-targeting: .NET 8 and .NET Standard 2.0
  • Symbol package generation for debugging
  • Generalized test data with intuitive structure representations

Usage

Basic Usage

using Phyros.OrganizationalUnits;

// Parse an OU string
var ou = OrganizationalUnit.Parse("world.country.region.city");

// Convert to string or URL-friendly string
string ouString = ou.ToString();          // "world.country.region.city"
string urlString = ou.ToUrlString();      // "world.country.region.city"

// Get fully qualified nodes - note the empty string as the first element
string[] nodes = ou.GetFullyQualifiedNodes();
// ["", "world", "world.country", "world.country.region", "world.country.region.city"]

// You can also implicitly convert between string and OrganizationalUnit
OrganizationalUnit implicitOu = "world.country";
string implicitString = implicitOu; // "world.country"

Using Custom Configuration

// Global configuration
OrganizationalUnitConfig.SetDefault(baseOrganizationalUnitAlias: "MyRoot", delimiter: '/');

// Parse with custom delimiter
var ou = OrganizationalUnit.Parse("world/country/region/city");
Console.WriteLine(ou.ToString());  // "world/country/region/city"

// Instance-specific configuration
var config = new OrganizationalUnitConfig { 
    BaseOrganizationalUnitAlias = "MyCompany", 
    Delimiter = '-' 
};

var customOu = OrganizationalUnit.Parse("dept-team-unit", config);
Console.WriteLine(customOu.ToUrlString());  // When empty: "MyCompany", otherwise: "dept-team-unit" 

Working with Base Organizational Unit

// Create a base organizational unit
var baseOu = new OrganizationalUnit();

// The base OU toString is empty string
Console.WriteLine(baseOu.ToString()); // ""

// The base OU toUrlString is the BaseOrganizationalUnitAlias
Console.WriteLine(baseOu.ToUrlString()); // "core"

// All of these are equivalent ways to create the base organizational unit
var baseOu1 = new OrganizationalUnit();
var baseOu2 = OrganizationalUnit.Parse("");
var baseOu3 = OrganizationalUnit.Parse("core");

Hierarchical Relationships

var parent = OrganizationalUnit.Parse("world.country");
var child = OrganizationalUnit.Parse("world.country.region");

// Check relationships
bool isDescendant = child.IsDescendantOf(parent);   // true
bool isAncestor = parent.IsAncestorOf(child);       // true
bool isChild = child.IsChildOf(parent);             // true
bool isParent = parent.IsParentOf(child);           // true

String Extensions

// You can directly convert a string to an organizational unit
using Phyros.OrganizationalUnits;

string path = "world.country.region";
var ou = path.ToOrganizationalUnit();

// This is equivalent to
var ou2 = OrganizationalUnit.Parse(path);

API Reference

OrganizationalUnit Class

Constructors
OrganizationalUnit(OrganizationalUnitConfig? config = null)

Creates an empty organizational unit representing the base.

var baseOu = new OrganizationalUnit();
Console.WriteLine(baseOu.ToUrlString()); // "core"
OrganizationalUnit(string organizationalUnitString, OrganizationalUnitConfig? config = null)

Creates an organizational unit from a string representation.

var ou = new OrganizationalUnit("world.country.region");
Console.WriteLine(ou.ToString()); // "world.country.region"
Properties
Nodes

Gets the nodes of this organizational unit in order from least specific to most specific.

var ou = new OrganizationalUnit("world.country.region");
foreach (var node in ou.Nodes)
{
    Console.WriteLine(node); // Outputs: "world", "country", "region"
}
Methods
Parse(string? organizationalUnitString, OrganizationalUnitConfig? config = null)

Parses a string into an OrganizationalUnit.

var ou = OrganizationalUnit.Parse("world.country.region");
Console.WriteLine(ou.ToString()); // "world.country.region"
GetFullyQualifiedNodes()

Gets fully qualified nodes in order from most general to most specific, with empty string as the first element.

var ou = OrganizationalUnit.Parse("world.country.region");
var nodes = ou.GetFullyQualifiedNodes();
// nodes = ["", "world", "world.country", "world.country.region"]
ToString()

Converts the organizational unit to a string representation.

var ou = OrganizationalUnit.Parse("world.country.region");
string str = ou.ToString(); // "world.country.region"

// Base organizational unit
var baseOu = new OrganizationalUnit();
string baseStr = baseOu.ToString(); // ""
ToUrlString()

Converts the organizational unit to a URL-friendly string, using the base organizational unit alias for empty units.

var ou = OrganizationalUnit.Parse("world.country.region");
string url = ou.ToUrlString(); // "world.country.region"

// Base organizational unit
var baseOu = new OrganizationalUnit();
string baseUrl = baseOu.ToUrlString(); // "core"
Equals(object? obj)

Determines whether this organizational unit is equal to another object.

var ou1 = OrganizationalUnit.Parse("world.country");
var ou2 = OrganizationalUnit.Parse("world.country");
bool equal = ou1.Equals(ou2); // true
GetHashCode()

Gets a hash code for the organizational unit for dictionary storage.

Implicit Conversions
  • implicit operator OrganizationalUnit(string): Converts a string to an OrganizationalUnit.
  • implicit operator string(OrganizationalUnit): Converts an OrganizationalUnit to its string representation.
// String to OrganizationalUnit
OrganizationalUnit ou = "world.country.region";

// OrganizationalUnit to string
string str = ou; // "world.country.region"

OrganizationalUnitConfig Class

Properties
Default

Gets the default configuration.

var defaultConfig = OrganizationalUnitConfig.Default;
Console.WriteLine(defaultConfig.BaseOrganizationalUnitAlias); // "core"
BaseOrganizationalUnitAlias

Gets or sets the alias for the base organizational unit (default: "core").

var config = new OrganizationalUnitConfig { BaseOrganizationalUnitAlias = "myroot" };
var ou = new OrganizationalUnit(config);
Console.WriteLine(ou.ToUrlString()); // "myroot"
Delimiter

Gets or sets the delimiter used to separate nodes (default: '.').

var config = new OrganizationalUnitConfig { Delimiter = '/' };
var ou = new OrganizationalUnit("world/country/region", config);
Methods
SetDefault(string? baseOrganizationalUnitAlias = DefaultBaseOrganizationalUnitAlias, char? delimiter = DefaultDelimiter)

Sets the default configuration values globally.

OrganizationalUnitConfig.SetDefault(baseOrganizationalUnitAlias: "myroot", delimiter: '/');
var ou = new OrganizationalUnit();
Console.WriteLine(ou.ToUrlString()); // "myroot"

OrganizationalUnitExtensions Class

Methods
IsDescendantOf(this OrganizationalUnit orgUnitInQuestion, OrganizationalUnit potentialParent)

Determines whether an organizational unit is a descendant of another.

var parent = OrganizationalUnit.Parse("world.country");
var child = OrganizationalUnit.Parse("world.country.region.city");
bool isDescendant = child.IsDescendantOf(parent); // true
IsAncestorOf(this OrganizationalUnit orgUnitInQuestion, OrganizationalUnit potentialDescendent)

Determines whether an organizational unit is an ancestor of another.

var parent = OrganizationalUnit.Parse("world.country");
var child = OrganizationalUnit.Parse("world.country.region.city");
bool isAncestor = parent.IsAncestorOf(child); // true
IsChildOf(this OrganizationalUnit orgUnitInQuestion, OrganizationalUnit potentialParent)

Determines whether an organizational unit is a direct child of another.

var parent = OrganizationalUnit.Parse("world.country");
var child = OrganizationalUnit.Parse("world.country.region");
bool isChild = child.IsChildOf(parent); // true
IsParentOf(this OrganizationalUnit orgUnitInQuestion, OrganizationalUnit potentialChild)

Determines whether an organizational unit is a direct parent of another.

var parent = OrganizationalUnit.Parse("world.country");
var child = OrganizationalUnit.Parse("world.country.region");
bool isParent = parent.IsParentOf(child); // true

StringExtensions Class

Methods
ToOrganizationalUnit(this string organizationalUnitString, OrganizationalUnitConfig? config = null)

Converts a string to an OrganizationalUnit.

string path = "world.country.region";
var ou = path.ToOrganizationalUnit();

Build & Test

  • Build locally:
    • Ctrl+Shift+B in VS Code (runs Nuke build)
    • Or: ./build.cmd (Windows) or ./build.sh (Linux/macOS)
  • Pack NuGet:
    • ./build.cmd Pack
  • Publish (CI only):
    • Handled by GitHub Actions on push/PR

CI/CD

Configuration

  • Global Settings:
    • Base OU Alias: Set at runtime via OrganizationalUnitConfig.Default.BaseOrganizationalUnitAlias or OrganizationalUnitConfig.SetDefault("MyRoot")
    • Delimiter: Set at runtime via OrganizationalUnitConfig.Default.Delimiter or OrganizationalUnitConfig.SetDefault(delimiter: '/')

Contributing

PRs and issues welcome! Please use generic test data with intuitive organizational structure representations.


© 2025 toadicusrex. MIT License.

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp2.0 was computed.  netcoreapp2.1 was computed.  netcoreapp2.2 was computed.  netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.0 is compatible.  netstandard2.1 was computed. 
.NET Framework net461 was computed.  net462 was computed.  net463 was computed.  net47 was computed.  net471 was computed.  net472 was computed.  net48 was computed.  net481 was computed. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen40 was computed.  tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.0

    • No dependencies.
  • net8.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.6 188 8/4/2025
1.0.5 68 8/2/2025
1.0.4 42 8/2/2025
1.0.3 48 8/2/2025
1.0.2 117 7/30/2025