{"id":48347897,"url":"https://github.com/abulka/relationship-manager","last_synced_at":"2026-04-05T08:02:48.047Z","repository":{"id":57460959,"uuid":"302232641","full_name":"abulka/relationship-manager","owner":"abulka","description":"Relationship Manager - lightweight Object Database class - Implementations for Python, C# and Java.","archived":false,"fork":false,"pushed_at":"2020-11-03T22:45:22.000Z","size":4262,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-10-27T07:52:33.895Z","etag":null,"topics":["database","design-pattern","object","python"],"latest_commit_sha":null,"homepage":"","language":"HTML","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/abulka.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-10-08T04:36:08.000Z","updated_at":"2020-11-03T22:45:24.000Z","dependencies_parsed_at":"2022-09-17T04:22:21.585Z","dependency_job_id":null,"html_url":"https://github.com/abulka/relationship-manager","commit_stats":null,"previous_names":[],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/abulka/relationship-manager","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abulka%2Frelationship-manager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abulka%2Frelationship-manager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abulka%2Frelationship-manager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abulka%2Frelationship-manager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/abulka","download_url":"https://codeload.github.com/abulka/relationship-manager/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/abulka%2Frelationship-manager/sbom","scorecard":{"id":162001,"data":{"date":"2025-08-11","repo":{"name":"github.com/abulka/relationship-manager","commit":"95547ce9f6890ac588203d06c8e24c94486d72e3"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":1.7,"checks":[{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"SAST","score":0,"reason":"no SAST tool detected","details":["Warn: no pull requests merged into dev branch"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}},{"name":"Code-Review","score":0,"reason":"Found 0/30 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Binary-Artifacts","score":6,"reason":"binaries present in source code","details":["Warn: binary detected: research/boo dotnet deprecated/doc/_misc/Booc/Boo.Lang.Compiler.dll:1","Warn: binary detected: research/boo dotnet deprecated/doc/_misc/Booc/Boo.Lang.Parser.dll:1","Warn: binary detected: research/boo dotnet deprecated/doc/_misc/Booc/Boo.Lang.dll:1","Warn: binary detected: research/boo dotnet deprecated/doc/_misc/Booc/booc.exe:1"],"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Signed-Releases","score":0,"reason":"Project has not signed or included provenance with any releases.","details":["Warn: release artifact v2.0.0 not signed: https://api.github.com/repos/abulka/relationship-manager/releases/32856670","Warn: release artifact v2.0.0 does not have provenance: https://api.github.com/repos/abulka/relationship-manager/releases/32856670"],"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'main'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"Vulnerabilities","score":9,"reason":"1 existing vulnerabilities detected","details":["Warn: Project is vulnerable to: PYSEC-2022-43017 / GHSA-qwmp-2cf2-g9g6"],"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}}]},"last_synced_at":"2025-08-16T13:44:49.069Z","repository_id":57460959,"created_at":"2025-08-16T13:44:49.069Z","updated_at":"2025-08-16T13:44:49.069Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31428645,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-05T02:22:46.605Z","status":"ssl_error","status_checked_at":"2026-04-05T02:22:33.263Z","response_time":75,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["database","design-pattern","object","python"],"created_at":"2026-04-05T08:02:47.355Z","updated_at":"2026-04-05T08:02:47.999Z","avatar_url":"https://github.com/abulka.png","language":"HTML","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Relationship Manager - a lightweight Object Database class\n\nA central mediating class which records all the one-to-one, one-to-many and many-to-many relationships between a group of classes. \n\n## What is it?\n\nClasses that use a Relationship Manager to implement their relationship properties and methods have a consistent metaphor and trivial implementation code (one line calls). In contrast - traditional \"pointer\" and \"arraylist\" techniques of implementing relationships are fully flexible but often require a reasonable amount of non-trivial code which can be tricky to get working correctly and are almost always a pain to maintain due to the detailed coding and coupling between classes involved, especially when back-pointers are involved.\n\nUsing a `Relationship Manager` object to manage the relationships can mitigate these problems and make managing relationships straightforward. It also opens up the possibility of powerful querying of relationships, a very simple version of something like [LINQ](https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/linq/).\n\nIn a sense, an [Object Database](https://en.wikipedia.org/wiki/Object_database)\nis an elaborate implementation of the Relationship Manager pattern. However the\n*intent* of the Relationship Manager pattern is lighter weight, to replace the\nwirings between objects rather than acting as a huge central database on disk -\nthough persistence is built into Relationship Manager too.\n\nHere are various implementations of the Relationship Manager Pattern in this GitHub repository:\n\n- Python: Uses Python 3, there are no dependencies.\n- C#: Used in at least one major commercial product. Implementations for .NET4 and .NET Core 3.1 (the latter project is cross platform and has been verified to run on Mac using [Visual Code for Mac](https://visualstudio.microsoft.com/vs/mac/)).\n- Java\n\n# Python\n\n## Installation\n\n```shell\npip install relationship-manager\n```\n\n## Usage\n\n```python\nfrom relmgr import RelationshipManager\n  \nrm = RelationshipManager()\nrm.enforce(\"xtoy\", \"onetoone\", \"directional\")\nx = object()\ny = object()\nrm.add_rel(x, y, \"xtoy\")\nassert rm.find_target(x, \"xtoy\") == y\n```\n\n- Read the unit tests to see all functionality being exercised, incl. backpointer queries. \n- See the examples below and in the `relmgr/examples/` directory of this repository.\n- See full [API documentation](https://abulka.github.io/relationship-manager/relmgr/index.html).\n- See the Relationship Manager pattern referred to above for lots more documentation.\n\n## Python API\n\nQuick summary of the v2 API:\n\n```python\ndef add_rel(self, source, target, rel_id: Union[int,str]=1) -\u003e None:\ndef remove_rel(self, source, target, rel_id=1) -\u003e None:\ndef enforce(self, rel_id, cardinality, directionality=\"directional\"):\ndef clear(self) -\u003e None:\n\n# query API\ndef find_targets(self, source, rel_id) -\u003e List:\ndef find_target(self, source, rel_id) -\u003e object:\ndef find_sources(self, target, rel_id) -\u003e List: # Back pointer query\ndef find_source(self, target, rel_id) -\u003e object: # Back pointer query\ndef find_rels(self, source, target) -\u003e List:\ndef is_rel(self, source, target, rel_id=1) -\u003e bool:\n\n# persistence related\nobjects: Namespace\nrelationships = property(_get_relationships, _set_relationships)  # flat list of rel. tuples\ndef dumps(self) -\u003e bytes:\ndef loads(asbytes: bytes) -\u003e RelationshipManager:  # @staticmethod\n```\n\nSee full [API documentation](https://abulka.github.io/relationship-manager/relmgr/index.html).\n\n## Hiding the use of Relationship Manager\n\nIts probably best practice to hide the use of Relationship Manager and simply use it as\nan implementation underneath traditional wiring methods like `.add()` and\n`setY()` or properties like `.subject` etc. \n\nFor example, to implement:\n```\n         ______________        ______________\n        |       X      |      |       Y      |\n        |______________|      |______________|\n        |              |      |              |\n        |void  setY(y) |1    1|              |\n        |Y     getY()  |-----\u003e|              |\n        |void  clearY()|      |              |\n        |______________|      |______________|\n```\n\nwrite the Python code like this:\n```python\nfrom relmgr import RelMgr\n\nRM = RelMgr()\n\nclass X:\n    def __init__(self):        rm.enforce(\"xtoy\", \"onetoone\", \"directional\")\n    def setY(self, y):         rm.add_rel(self, y, \"xtoy\")\n    def getY(self):     return rm.find_target(source=self, rel_id=\"xtoy\")\n    def clearY(self):          rm.remove_rel(self, self.getY(), \"xtoy\")\n\nclass Y:\n    pass\n```\n\nNote the use of the abbreviated Relationship Manager API in this example.\n\n### Another example\n\nHere is another example of hiding the use of Relationship Manager, \nfound in the examples folder as `relmgr/examples/observer.py` - the\nclassic Subject/Observer pattern:\n\n```python\nfrom relmgr import RelationshipManager\n\n\nrm = RelationshipManager()\n\n\nclass Observer:\n   \n    @property\n    def subject(self):\n        return rm.find_target(self)\n\n    @subject.setter\n    def subject(self, _subject):\n        rm.add_rel(self, _subject)\n\n    def notify(self, subject, notification_type):\n        pass  # implementations override this and do something\n\n\nclass Subject:\n\n    def notify_all(self, notification_type: str):\n        observers = rm.find_sources(self)  # all things pointing at me\n        for o in observers:\n            o.Notify(self, notification_type)\n\n    def add_observer(self, observer):\n        rm.add_rel(observer, self)\n\n    def remove_observer(self, observer):\n        rm.remove_rel(source=observer, target=self)\n\n```\n\nWhen using the Subject and Observer, you use their methods without realising their functionality has been implemented using rm.  See `tests/python/examples/test_observer.py` in the GitHub project for the unit tests for this code.\n\n## Persistence\n\nThe easiest approach to persistence is to use the built in `dumps` and `loads`\nmethods of `RelationshipManager`. Relationship Manager also provides an attribute\nobject called `.objects` where you should keep all the objects involved in\nrelationships e.g.\n\n```python\nrm.objects.obj1 = Entity(strength=1, wise=True, experience=80)\n```\n\nThen when you persist the Relationship Manager both the objects and\nrelations are pickled and later restored. This means your objects are\naccessible by attribute name e.g. `rm.objects.obj1` at all times. You can\nassign these references to local variables for convenience e.g. `obj1 = rm.objects.obj1`.\n    \nHere is complete example of creating three entitys, wiring them up, \npersisting them then restoring them:\n\n```python\nimport pprint\nimport random\nfrom dataclasses import dataclass\nfrom relmgr import RelationshipManager\n\n@dataclass\nclass Entity:\n    strength: int = 0\n    wise: bool = False\n    experience: int = 0\n\n    def __hash__(self):\n        hash_value = hash(self.strength) ^ hash(\n            self.wise) ^ hash(self.experience)\n        return hash_value\n\n\nrm = RelationshipManager()\nobj1 = rm.objects.obj1 = Entity(strength=1, wise=True, experience=80)\nobj2 = rm.objects.obj2 = Entity(strength=2, wise=False, experience=20)\nobj3 = rm.objects.obj3 = Entity(strength=3, wise=True, experience=100)\n\nrm.add_rel(obj1, obj2)\nrm.add_rel(obj1, obj3)\nassert rm.find_targets(obj1) == [obj2, obj3]\n\n# persist\nasbytes = rm.dumps()\n\n# resurrect\nrm2 = RelationshipManager.loads(asbytes)\n\n# check things worked\nnewobj1 = rm2.objects.obj1\nnewobj2 = rm2.objects.obj2\nnewobj3 = rm2.objects.obj3\nassert rm2.find_targets(newobj1) == [newobj2, newobj3]\nassert rm2.find_target(newobj1) is newobj2\n\nprint('done, all OK')\n```\n\n### Persistence API\n\nAs a reminder, the persistence API of `RelationshipManager` is:\n\n```python\nobjects: Namespace  \n\ndef dumps(self) -\u003e bytes:\n\n@staticmethod\ndef loads(asbytes: bytes) -\u003e RelationshipManager:\n```\n\nPlease create attributes on the `objects` property of the relationship manager, pointing to those objects involved in relationships. It is however optional, and only provides a guaranteed way of pickling and persisting the objects involved in the relationships along with the relationships themselves, when persisting the relationship manager.  \n\nYou could attach your other application state to this `objects` property of the relationship manager and thus save your entire application state into the same file.  Alternively save the pickeled bytes into your own persistence file solution.\n\nThere are currently no `dump()` or `load()` methods implemented, which would pickle\nto and from a *file*. These can easily be added in a subclass or just write and\nread the results of the existing `dumps()` and `loads()` methods to a file\nyourself, as bytes.\n\n### Manual Control of Persistence\n\nPersistence can be a bit tricky because you need to persist both objects and relationships between those objects.\n\nOther libraries that implement models, schemas, serializers/deserializers,\nODM's/ORM's, Active Records or similar patterns will require you to define your\nclasses in a particular way. Relationship Manager works with any Python objects\nlike dataclass objects etc. without any special decoration or structure\nrequired.\n\nWhilst it is possible to simply pickle a Relationship Manager instance and\nrestore it, you won't have easy access to the objects involved. Sure,\nRelationship Manager will return objects which have been resurrected from\npersistence correctly but how, in such a unpickled situation, will you pass\nobject instances to the Relationship Manager API? Thus its better to prepare\nyour persitence properly and store all your objects in a dictionary or object\nand pickle that together with the Relationship Manager.  E.g.\n\nFor code examples of custom persistence, see \n`research/python persistence research/persist_pickle_simple.py`\nas well as other persistence approaches in that directory, including an approach which \nstores objects in dictionaries with ids and uses the Relationship Manager to store relationships between those ids, rather than relationships between object references.\n\n## Running the tests\n\n### Python tests\n\nCheck our this project from GitHub, open the project directory in vscode and there is a local `settings.json` and `launch.json` already populated which means you can choose the launch profile `Run all tests: using -m unittest` or use the vscode built in GUI test runner (hit the `Discover Tests` button then the `Run all tests` button).\n\nOr simply:\n\n```shell\npython -m unittest discover -p 'test*' -v tests\n```\n\nSee below on information on running the C# and Java tests.\n \n## Appendix: Installing Python into a new virtual environment\n\nEither use `pipenv` to manage a new virtual environment or use Python's built in `venv`:\n\n```shell\nmkdir proj1\ncd proj1\npython -m venv env\n\nenv/bin/pip install relationship-manager\nenv/bin/python\n\u003e from relmgr import RelationshipManager\n```\n\nYou can activate the virtual environment after you create it, which makes running `pip` and `python` etc. easier\n\n```\nmkdir proj1\ncd proj1\npython -m venv env\n\nsource env/bin/activate\npip install relationship-manager\npython\n\u003e from relmgr import RelationshipManager\n```\n\n# Final Thoughts on the Python Implementation\n\n## References and memory\n\nBe careful - the Relationship Manager will have references to your objects so garbage collection may not be able to kick in. If you remove all relationships for an object it should be removed from the Relationship Manager, but this needs to be verified in these implementations.\n\n## Performance\n\nBe mindful that normal object to object wiring using references and lists of references is going to be much faster than a Relationship Manager.\n\nYou can have multiple relationship manager instances to manage different areas of your programming domain, which increases efficiency and comprehensibility.\n\nYou may want to google for other more professional [Object Databases](https://en.wikipedia.org/wiki/Object_database). For example, in the Python space we have:\n\n- A [curated list](https://github.com/grundic/awesome-python-models) of awesome Python libraries, which implement models, schemas, serializers/deserializers, ODM's/ORM's, Active Records or similar patterns.\n- A peek at [three Python databases](https://www.opensourceforu.com/2017/05/three-python-databases-pickledb-tinydb-zodb/): PickleDB, TinyDB and ZODB. Also see the [welcome to TinyDB](https://tinydb.readthedocs.io/en/stable/usage.html#queries), your tiny, document oriented database optimized for your happiness, and a [tutorial on ZODB](http://www.zodb.org/en/latest/tutorial.html#tutorial-label).\n- [Axiom](https://divmod.readthedocs.io/en/latest/products/axiom/index.html) is an object database whose primary goal is to provide an object-oriented layer to an SQL database.\n- [Newt DB](http://www.newtdb.org/en/latest/getting-started.html) - You’ll need a Postgres Database server.\n\nHowever most of these need a backing SQL database - Relationship Manager does not, which may be a benefit - no databases to set up - just get on with coding!\n\n# C# and Java implementations of Relationship Manager \n\nIn this Github repository there are several other implementations of Relationship Manager. Currently only the Python implementation uses the newer v2 API and implementation. The C# and Java implementations use the v1 API.\n\nThe C# and Java implementations have a strict, typed API interface `IRelationshipManager` to talk to.\n\n## C#\n\nA fast implementation for .NET - has been used in a major commercial project.\n- `csharp-net4` uses the .NET 4 framework.\n- `csharp-netcore` is the same code, using the new .NET Core 3.1 framework. This solution also loads and runs OK in [Visual Code for Mac](https://visualstudio.microsoft.com/vs/mac/).\n \n## Java\n\nA Java implementation.\n\n- Use [IntelliJ](https://www.jetbrains.com/idea/) to open the relmgr_java project (`.idea/` contains the project config, as well as a couple of `.iml` files).\n    - The `test all` run configuration is for running the tests. \n    - The `Customer Orders Main console app` run configuration is a console app for the Customer - Order Example.\n- Or use [Netbeans](https://netbeans.org/) (`nbproject/` contains each project config).  There are two separate projects from Netbeans' point of view, so open them both. You can open multiple projects at the same time in Netbeans:\n    - `relmgr_java\\RelationshipManager-Java`\n    - `relmgr_java\\ExampleCustomerOrders-Java`\n\nMany thanks to *Tarik* for porting from C# to Java.\n\n## C# and Java tests\n\nOpen either the C# or Java projects and run the unit tests from your IDE. \n\n\u003e Using the free, Community Edition of either Visual Studio or IntelliJ is fine.\n\n- C# - use [Visual Studio for Windows](https://visualstudio.microsoft.com/vs/) or [Visual Studio for Mac](https://visualstudio.microsoft.com/vs/mac/) and run the tests using the UI test runner.\n    - If on Mac, ensure you open the `relmgr_dotnet\\csharp-netcore` cross-platform project.\n    - Using the free, lighter weight [Visual Studio Code](https://code.visualstudio.com/) and `dotnet` CLI is also probably possible. \n- Java\n    - The unit tests use Junit4 (not the later Junit5).\n    - Use [IntelliJ](https://www.jetbrains.com/idea/) to open the `relmgr_java` project and run the `test all` test configuration.\n    - Or use [Netbeans](https://netbeans.org/) to open the `relmgr_java\\RelationshipManager-Java` project - right click on the file `relmgr_java\\RelationshipManager-Java\\test\\RunAllTests.java` and select `Test File` from the popup menu.\n\n## C# and Java API\n\n\u003e Note: C# and Java implementations use the original older v1 API, not the new v2 API implemented in Python, above.\n\n```java\npublic enum Cardinality  \n{  \n    OneToOne,  \n    OneToMany,  \n    ManyToOne,  \n    ManyToMany  \n}\n\npublic enum Directionality  \n{  \n    UniDirectional,  \n    DirectionalWithBackPointer,  \n    DoubleDirectional  \n}\n\ninterface IRelationshipManager {\n  void AddRelationship(object fromObj, object toObj, string relId);  \n  void AddRelationship(object fromObj, object toObj);  // overloaded, uses a default relId\n  void EnforceRelationship(string relId, Cardinality cardinality);    // overloaded, uses a default directionality\n  void EnforceRelationship(string relId, Cardinality cardinality, Directionality directionality);  \n  IList FindObjectsPointedToByMe(object fromObj, string relId);  \n  object FindObjectPointedToByMe(object fromObj, string relId);  \n   IList FindObjectsPointingToMe(object toObj, string relId);  \n  object FindObjectPointingToMe(object toObj, string relId);  \n  void RemoveRelationship(object fromObj, object toObj, string relId);  \n  void RemoveAllRelationshipsInvolving(object obj, string relId);  \n  int Count();  \n  int CountRelationships(string relId);  \n  void Clear();  \n  bool DoesRelIdExistBetween(object fromObj, object toObj, string relId);  \n  IList FindRelIdsBetween(object fromObj, object toObj);\n}\n```\n\nBoth `from` and `to` (aka. `source` and `target`) are *your* object instances that you want to record a relationship between.\nAfter a Find... call you may need to `cast` to a type, as I think every object is stored as a `object` type. Relationship Manager really needs to use generics to avoid this. The Python implementation doesn't need to deal with this issue because it is dynamically typed.\n\n### C# and Java abbreviated API\n\nThe abbreviated API is more succinct, but is only recommended for unit tests. In the abbreviated API:\n- `f` means `from` (or the more modern v2. API name of `source`).\n- `t` means `to` (or the more modern v2. API name of `target`). It is one of *your* object instances.\n\n```csharp\npublic interface IRM\n{\n    object B(object toObj, string relId);\n    System.Collections.IList BS(object toObj, string relId);\n    void ER(string relId, Cardinality cardinality);\n    void ER(string relId, Cardinality cardinality, Directionality directionality);\n    void NR(object fromObj, object toObj, string relId);\n    void NRS(object obj, string relId);\n    object P(object fromObj, string relId);\n    System.Collections.IList PS(object fromObj, string relId);\n    void R(object fromObj, object toObj, string relId);\n    bool QR(object fromObj, object toObj, string relId);\n    int Count();\n    int CountRelationships(string relId);\n    void Clear();\n}\n```\n\n### API v1 and v2 equivalence table\n\nThis table also shows the equivalent modern Python v2 API call for each method in the C#/Java v1 API.\n\n| Return Type            | C#/Java Method (v1 API) | Abbreviated (v1) | Python (v2 API)            |\n|------------------------|-------------------------|------------------|----------------------------|\n| void   | `AddRelationship`(from, to, id)       | `R`(f, t, id)        | `add_rel`(source, target, rel_id=1) -\u003e None |\n| void   | `RemoveRelationship`(from, to, id)    | `NR`(f, t, id)       | `remove_rel`(source, target, rel_id=1) -\u003e None |\n| void   | `RemoveAllRelationshipsInvolving`(object, id) | `NRS`(o, id) | `remove_rel`(source, target, rel_id=1) -\u003e None |\n| List   | `FindObjectsPointedToByMe`(from, id)  | `PS`(f, id)          | `find_targets`(source, rel_id=1) -\u003e List |\n| object | `FindObjectPointedToByMe`(from, id)   | `P`(f, id)           | `find_target`(source, rel_id=1) -\u003e object |\n| List   | `FindObjectsPointingToMe`(to, id)     | `BS`(t, id)          | `find_sources`(target, rel_id=1) -\u003e List |\n| object | `FindObjectPointingToMe`(to, id)      | `B`(t, id)           | `find_source`(target, rel_id=1) -\u003e object |\n| void   | `EnforceRelationship`(id, cardinality, bidirectionality) | `ER`(id, c, bi) | `enforce`(rel_id, cardinality, directionality=\"directional\") |\n| bool   | `DoesRelIdExistBetween`(from, to, id) | `QR`(f, t, id)       | `is_rel`(source, target, rel_id=1) -\u003e bool |\n| IList  | `FindRelIdsBetween`(from, to)         | -                    | `find_rels`(self, source, target) -\u003e List |\n| void   | `Clear`()                             | `Clear`()            | `clear`(self) -\u003e None |\n| void   | `Count`()                             | `Count`()            | - |\n| void   | `CountRelationships`(id)              | `CountRelationships`(id) | - |\n|        | -                                     | -                    | `relationships` *property* |\n|        | -                                     | -                    | `dumps`() -\u003e bytes |\n|        | -                                     | -                    | `loads`(asbytes: bytes): -\u003e RelationshipManager |\n\n### C# and Java - Finding just one object\n\nThe pair of find methods `FindObjectPointedToByMe()` and `FindObjectPointedToByMe()` only find _one_ object (even though there may be more).  This is a commonly used convenience method - the more painful way would be to use `FindObjectsPointingToMe()` and just grab the first object from the returned list.\nExactly which object is found is undefined, but would typically be the first one added.\n\n### C# and Java - Relationship Id\n\nWhat to use as the Relationship Id?\n\nThis is traditionally either an `integer` or a `string` in the Python implementation.  I have chosen to only use a `string` type in the C# and Java implementations, since you can describe relationships easily in this way rather than having to map from an integer back to some meaningful description.\n\n```csharp\nrm.AddRelationship(fromObject, toObject, relationshipId)\n```\n\nwill raise an exception if relationshipId is an empty string.  TODO: Presumably an empty relationship id should be treated like in Python, as the default relationship.\n\nTODO: The use of \"\\*\" as relationship id which matches any relationship needs to be verified in the C# and Java implementations, (not sure its implemented or needed) so please disregard the following comment for now: ~~All other functions (except for `AddRelationship`) can pass either an empty string or \"\\*\" as the `relationshipId`, which means you are searching for any relationship at all.  You would usually only want to do this if there is only _one_ relationship between class X and class Y, then your `P` and `NR` calls can specify \"\\*\" as the `relationshipId` in order to match any relationship between these two objects.  Alternatively, you can use relationship manager's overloaded versions of all its routines (except for `AddRelationship`) which don't take a `relationshipId` where `relationshipId` defaults to \"\\*\".~~\n\n### C# Usage\n\nIn your project, add references to\n\n![Adding refs](http://www.andypatterns.com/files/85941233059578rmdotnetRefs1.png)\n\nand in your code add the using clauses:\n\n```csharp\nusing RelationshipManager.Interfaces;\nusing RelationshipManager.Turbo;\n```\n\nThere are a number of directories in the project, but you really only need to reference the two dll's \n\n```\nRelationship Manager Interfaces.dll\nRelationship Manager Turbo.dll\n```\n\nto use Relationship Manager.\n\n#### Example 1\n\n```csharp\nprivate IRelationshipManager rm;\nrm = new RelationshipMgrTurbo();\nrm.AddRelationship('a', 'b', \"rel1\");\nIList list = rm.FindObjectsPointedToByMe('a', \"rel1\");\nAssert.AreEqual(list[0], 'b');\n```\n\n#### Example 2\n\n```csharp\nprivate IRelationshipManager rm;\nrm = new RelationshipMgrTurbo();\nrm.AddRelationship('a', 'b', \"rel1\");\nrm.AddRelationship('a', 'c', \"rel1\");\nrm.AddRelationship('a', 'z', \"rel2\");\nIList list;\nlist = rm.FindObjectsPointedToByMe('a', \"rel1\");\nchar[] expected = {'b', 'c'};\nAssert.AreEqual(list.ToString(), new ArrayList(expected).ToString());\nlist = rm.FindObjectsPointedToByMe('a', \"rel2\");\nAssert.AreEqual(list[0], 'z');\n```\n\n#### Example Person Order\n\nThis example project is available the .net core version of the Relationship Manager C# implementation. Note that the use of Relationship Manager is hidden, and is a mere implementation detail.\n\n```csharp\nusing System;\nusing System.Collections;\nusing System.Collections.Generic;\nusing RelationshipManager.Interfaces;\nusing RelationshipManager.Turbo;\n\nnamespace Example_Person_Order_Console_App\n{\n    class Program\n    {\n        static void Main(string[] args)\n        {\n            var jane = new Person(\"Jane\");\n            var order1 = new Order(\"Boots\");\n            var order2 = new Order(\"Clothes\");\n            jane.AddOrder(order1);\n            jane.AddOrder(order2);\n\n            // test forward pointer wiring\n            Console.WriteLine(jane + \" has \" + jane.GetOrders().Count + \" orders\");\n\n            // test the backpointer wiring\n            foreach (var order in jane.GetOrders())\n            {\n                Console.WriteLine(\"The person who ordered \" + order + \" is \" + order.GetPerson());\n            }\n\n            Console.WriteLine(\"Done!\");\n\n        }\n\n        ///   \n        /// BO is the base Business Object class which holds a single static reference  \n        /// to a relationship manager. This one relationship manager is  \n        /// used for managing all the relationships between Business Objects\n        /// like Person and Order.  \n        ///   \n        public class BO // Base business object  \n        {\n            static protected RelationshipMgrTurbo rm = new RelationshipMgrTurbo();\n        }\n\n\n        ///   \n        /// Person class points to one or more orders.  \n        /// Implemented using a relationship manager rather   \n        /// than via pointers and arraylists etc.  \n        ///   \n        public class Person : BO\n        {\n            public string name;\n\n            static Person()\n            {\n                rm.EnforceRelationship(\"p-\u003eo\", Cardinality.OneToMany, Directionality.DirectionalWithBackPointer);\n            }\n\n            public Person(string name)\n            {\n                this.name = name;\n            }\n\n            public override string ToString()\n            {\n                return \"Person: \" + this.name;\n            }\n\n            public void AddOrder(Order o)\n            {\n                rm.AddRelationship(this, o, \"p-\u003eo\");\n            }\n\n            public void RemoveOrder(Order o)\n            {\n                rm.RemoveRelationship(this, o, \"p-\u003eo\");\n            }\n\n            public List\u003cOrder\u003e GetOrders()\n            {\n                IList list = rm.FindObjectsPointedToByMe(this, \"p-\u003eo\");\n\n                // cast from list of 'object' to list of 'Person'\n                var result = new List\u003cOrder\u003e();\n                foreach (var order in list)\n                    result.Add((Order)order);\n\n                // attempts at other simpler ways to cast a whole list\n                //result = list as List\u003cOrder\u003e;  // crash\n                //result = new List\u003cOrder\u003e(list); // syntax error?\n\n                return result;\n            }\n        }\n\n        ///   \n        /// Order class points back to the person holding the order.  \n        /// Implemented using a relationship manager rather\n        /// than via pointers and arraylists etc.  \n        ///  \n        public class Order : BO\n        {\n            public string description;\n\n            public Order(string description)\n            {\n                this.description = description;\n            }\n\n            public override string ToString()\n            {\n                return \"Order Description: \" + this.description;\n            }\n\n            public void SetPerson(Person p)\n            {\n                // though mapping is bidirectional, there is still a primary relationship direction!\n                rm.AddRelationship(p, this, \"p-\u003eo\");\n\n            }\n\n            public Person GetPerson()\n            {\n                // cast from 'object' to 'Person'\n                return (Person)rm.FindObjectPointingToMe(this, \"p-\u003eo\");\n            }\n\n            public void ClearPerson()\n            {\n                rm.RemoveRelationship(this, this.GetPerson(), \"p-\u003eo\");\n            }\n        }\n\n    }\n\n}\n```\n\nOutput:\n\n```\nPerson: Jane has 2 orders\nThe person who ordered Order Description: Clothes is Person: Jane\nThe person who ordered Order Description: Boots is Person: Jane\nDone!\n```\n\n\u003e A similar example for Java is available in `relmgr_java/ExampleCustomerOrders-Java/`.\n\nA generics version of relationship manager would be cool - that way no casting would be required. Presently all calls to relationship manager return objects or lists of objects - which you have to cast to the specific type you actually have stored. You can see this casting in the above example code.\n\n#### Other C# examples\n\nStudy the unit tests to see how to drive this library even further.\n\n### UML for the C# implementation of Relationship Manager\n\n![UML class diagram](http://www.andypatterns.com/files/60481233059254rmdotnetuml1.png)\n\n*UML for the C# version of Relationship Manager.*\n\n# Resources\n\n- Full [API documentation](https://abulka.github.io/relationship-manager/relmgr/index.html).\n\n- Official [Relationship Manager Pattern](https://abulka.github.io/projects/patterns/relationship-manager/) page incl. academic paper by Andy Bulka.\n\n- Python Implementation [README](https://github.com/abulka/relationship-manager) (this page) and [GitHub project](https://github.com/abulka/relationship-manager).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabulka%2Frelationship-manager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fabulka%2Frelationship-manager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fabulka%2Frelationship-manager/lists"}