{"id":16964371,"url":"https://github.com/bolorundurowb/meerkat","last_synced_at":"2025-03-22T14:30:59.069Z","repository":{"id":38275357,"uuid":"413827599","full_name":"bolorundurowb/meerkat","owner":"bolorundurowb","description":"A library aiming to implement functionality similar to NodeJS's mongoose","archived":false,"fork":false,"pushed_at":"2025-02-26T15:20:05.000Z","size":100,"stargazers_count":8,"open_issues_count":0,"forks_count":1,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-03-18T11:52:00.689Z","etag":null,"topics":["csharp","dotnet","mongodb","odm"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bolorundurowb.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-10-05T13:23:04.000Z","updated_at":"2025-02-26T15:20:09.000Z","dependencies_parsed_at":"2025-03-02T13:00:45.717Z","dependency_job_id":null,"html_url":"https://github.com/bolorundurowb/meerkat","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bolorundurowb%2Fmeerkat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bolorundurowb%2Fmeerkat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bolorundurowb%2Fmeerkat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bolorundurowb%2Fmeerkat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bolorundurowb","download_url":"https://codeload.github.com/bolorundurowb/meerkat/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244971763,"owners_count":20540850,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":["csharp","dotnet","mongodb","odm"],"created_at":"2024-10-13T23:43:25.050Z","updated_at":"2025-03-22T14:30:59.062Z","avatar_url":"https://github.com/bolorundurowb.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# 🐾 Meerkat\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) ![NuGet Version](https://img.shields.io/nuget/v/meerkat) [![Build Status](https://app.travis-ci.com/bolorundurowb/meerkat.svg?branch=master)](https://app.travis-ci.com/bolorundurowb/meerkat)\n\n**Meerkat** is an ODM (Object Document Mapper) library designed to replicate the functionality of NodeJS's [Mongoose](https://www.npmjs.com/package/mongoose) in the .NET ecosystem. 🚀 For those unfamiliar, Mongoose is a JavaScript ODM wrapper library that simplifies data access when working with MongoDB. Similarly, **Meerkat** wraps around the official MongoDB client library for .NET, simplifying common data access patterns.\n\nThe name **Meerkat** is a playful homage to Mongoose, as a meerkat is a type of mongoose. 😄 If you find this library cool or useful, don't forget to give it a ⭐️ star!\n\n---\n\n## 🚨 Breaking Changes\n\nWith the release of **version 2.0.0**, the underlying MongoDB driver was upgraded to **3.2.0**. The library also transitions the base `Schema` class to  using strongly typed Ids\n\n---\n\n## 🤝 Contributing\n\nThere’s still a lot to be done! Feel free to:\n- Open new issues to suggest features or report bugs 🐛\n- Submit PRs with updates or improvements 🛠️\n\n---\n\n## 📦 Installation\n\n### Manual Installation (for the hardcore devs 💪)\nAdd the following to your `.csproj` file:\n\n```xml\n\u003cPackageReference Include=\"meerkat\" Version=\"2.0.0\"/\u003e\n```\n\n### Visual Studio Package Manager Console\nRun the following command:\n\n```cmd\nInstall-Package meerkat\n```\n\n### .NET CLI\nRun the following in your terminal:\n\n```bash\ndotnet add package meerkat\n```\n\n---\n\n## 🛠️ Setup\n\nBefore using any of Meerkat's functions, you need to initialize it. This only needs to be done once. 🏁\n\n```csharp\nusing meerkat;\n...\nMeerkat.Connect(\"\u003cany valid full MongoDB connection string\u003e\"); // e.g., mongodb://user:password@server-address:port/database-name?other-options\n```\n\n---\n\n## 🚀 Usage\n\nEnsure you’ve declared the necessary namespace at the top of your class file:\n\n```csharp\nusing meerkat;\n```\n\n**Note:** All async methods support `CancellationToken` for canceling operations. ⏳\n\n---\n\n### 🧩 Modelling\n\nAll models must inherit from the abstract `Schema` class. The `Schema` class has a an `Id` property whose type is determined by the `TKey` generic argument. In the example below, the `Id` property is of type `ObjectId`.\n\n```csharp\nclass Student : Schema\u003cObjectId\u003e\n{  \n  public string FirstName { get; set; }\n  \n  public string LastName { get; set; }\n  \n  public Student()\n  {\n    // Example: Generate an ObjectID (you'd likely use a methode better suited to your Id type)\n    Id = ObjectId.GenerateNewId();\n  }\n}\n```\n\nTo specify a custom collection name or enable timestamp tracking:\n\n```csharp\n[Collection(Name = \"Persons\", TrackTimestamps = true)]\npublic class Student : Schema\n{\n  ...\n}\n```\n\n---\n\n### 💾 Persistence\n\nMeerkat simplifies CRUD operations by combining **create** and **update** into a single API. If an entity doesn’t exist, it’s inserted; if it does, it’s updated. 🔄\n\n```csharp\nvar student = new Student\n{\n  FirstName = \"Olubakinde\",\n  LastName = \"Chukumerije\"\n};\n\nawait student.SaveAsync(); // or student.Save(); for synchronous calls\n```\n\nIt’s that simple! 🎉\n\n---\n\n### 🔍 Querying\n\n#### Find by ID\n```csharp\nvar student = await Meerkat.FindByIdAsync\u003cStudent\u003e(1234); // or Meerkat.FindById\u003cStudent\u003e(1234); for sync calls\n```\n\n#### Find by Predicate\n```csharp\nvar student = await Meerkat.FindOneAsync\u003cStudent\u003e(x =\u003e x.FirstName == \"John\"); // or Meerkat.FindOne(x =\u003e x.LastName == \"Jane\");\n```\n\n#### Complex Queries\nFor complex queries, you can access the underlying `IQueryable`:\n\n```csharp\nvar queryable = Meerkat.Query\u003cStudent\u003e();\n\nvar students = await queryable\n  .Where(x =\u003e x.FirstName == \"Olubakinde\")\n  .ToListAsync();\n```\n\n---\n\n### 🗑️ Removal\n\n#### Remove by ID\n```csharp\nawait Meerkat.RemoveByIdAsync\u003cStudent\u003e(1234); // or Meerkat.RemoveById\u003cStudent\u003e(1234); for sync calls\n```\n\n#### Remove by Predicate\n```csharp\nawait Meerkat.RemoveOneAsync\u003cStudent\u003e(x =\u003e x.FirstName == \"John\"); // or Meerkat.RemoveOne(x =\u003e x.LastName == \"Jane\");\n```\n\n#### Remove All Matching Entities\n```csharp\nawait Meerkat.RemoveAsync\u003cStudent\u003e(x =\u003e x.FirstName == \"John\"); // or Meerkat.Remove(x =\u003e x.LastName == \"Jane\");\n```\n\n---\n\n### ✅ Existence Checks\n\n#### Check if Any Entities Exist\n```csharp\nvar exists = await Meerkat.ExistsAsync\u003cStudent\u003e(); // or Meerkat.Exists\u003cStudent\u003e(); for sync calls\n```\n\n#### Check if Entities Match a Predicate\n```csharp\nvar exists = await Meerkat.ExistsAsync\u003cStudent\u003e(x =\u003e x.FirstName.StartsWith(\"Ja\")); // or Meerkat.Exists\u003cStudent\u003e(x =\u003e x.FirstName.StartsWith(\"Ja\"));\n```\n\n---\n\n### 🔢 Counting\n\n#### Count All Entities\n```csharp\nvar count = await Meerkat.CountAsync\u003cStudent\u003e(); // or Meerkat.Count\u003cStudent\u003e(); for sync calls\n```\n\n#### Count Entities Matching a Predicate\n```csharp\nvar count = await Meerkat.CountAsync\u003cStudent\u003e(x =\u003e x.FirstName.StartsWith(\"Ja\")); // or Meerkat.Count\u003cStudent\u003e(x =\u003e x.FirstName.StartsWith(\"Ja\"));\n```\n\n---\n\n### 📚 Collections\n\nMeerkat allows for bulk upsert operations on collections of entities, both synchronously and asynchronously. 📦\n\n```csharp\nvar peter = new Student();\nvar paul = new Student();\nvar students = new [] { peter, paul };\nawait students.SaveAllAsync(); // or students.SaveAll(); for sync calls\n```\n\n## 📄 Index Attributes  \n\nThis section provides an overview of the custom attributes used to define MongoDB indexes in your model classes. These attributes are applied to fields or properties and are used to generate appropriate indexes in MongoDB. All model classes must inherit from the abstract class `Schema\u003cTKey\u003e`.\n\n---\n\n### 🗂️ Index Attributes Overview \n\n#### 🎯 **`SingleFieldIndexAttribute`** \n- **Purpose**: Defines a single-field index on a specific field or property.\n- **Usage**: Apply this attribute to a field or property to create an index on that single field.\n- **Optional Properties**:\n    - **`Name`**: Specifies the name of the index. If not provided, MongoDB generates a default name.\n    - **`Sparse`**: A boolean value indicating whether the index should be sparse. Default is `false`.\n    - **`IndexOrder`**: Specifies the order of the index. Options are `Ascending`, `Descending`, or `Hashed`.\n\n##### Example:\n```csharp\npublic class User : Schema\u003cGuid\u003e\n{\n    [SingleFieldIndex(Name = \"username_index\", Sparse = true, IndexOrder = IndexOrder.Ascending)]\n    public string Username { get; set; }\n}\n```\n- **Explanation**: This creates a single-field index on the `Username` property with an ascending order. The index is sparse, meaning it will only include documents where the `Username` field exists.\n\n##### What is a Sparse Index? 🤔\nA sparse index only includes documents that have the indexed field. If a document does not contain the indexed field, it is excluded from the index. This can save space and improve performance for fields that are not present in all documents.\n\n##### What is a Hashed Index? 🔍\nA hashed index in MongoDB uses a hash function to compute the value of the indexed field. This is particularly useful for sharding and equality queries but does not support range queries.\n\n---\n\n#### 🔑 **`UniqueIndexAttribute`** \n- **Purpose**: Defines a unique index on a specific field or property.\n- **Usage**: Apply this attribute to enforce uniqueness on a field or property.\n- **Optional Properties**:\n    - **`Name`**: Specifies the name of the index. If not provided, MongoDB generates a default name.\n    - **`Sparse`**: A boolean value indicating whether the index should be sparse. Default is `false`.\n\n##### Example:\n```csharp\npublic class User : Schema\u003cGuid\u003e\n{\n    [UniqueIndex(Name = \"email_unique_index\", Sparse = true)]\n    public string Email { get; set; }\n}\n```\n- **Explanation**: This creates a unique index on the `Email` property. The index is sparse, meaning it will only include documents where the `Email` field exists.\n\n---\n\n#### 🧩 **`CompoundIndexAttribute`** \n- **Purpose**: Defines a compound index on multiple fields or properties.\n- **Usage**: Apply this attribute to multiple fields or properties to create a compound index.\n- **Optional Properties**:\n    - **`Name`**: Specifies the name of the index. If two or more fields have the same `Name`, they are grouped into a single compound index. Unnamed indexes are grouped into one compound index.\n    - **`IndexOrder`**: Specifies the order of the index. Options are `Ascending`, `Descending`, or `Hashed`.\n\n##### Example:\n```csharp\npublic class Order : Schema\u003cGuid\u003e\n{\n    [CompoundIndex(Name = \"order_index\", IndexOrder = IndexOrder.Ascending)]\n    public DateTime OrderDate { get; set; }\n\n    [CompoundIndex(Name = \"order_index\", IndexOrder = IndexOrder.Descending)]\n    public decimal TotalAmount { get; set; }\n}\n```\n- **Explanation**: This creates a compound index on the `OrderDate` and `TotalAmount` properties. The `OrderDate` is indexed in ascending order, while the `TotalAmount` is indexed in descending order.\n\n##### Note on Compound Indexes 📌\nIf multiple fields have the same `Name` in the `CompoundIndexAttribute`, they are grouped into a single compound index. Unnamed indexes are grouped into one compound index automatically.\n\n---\n\n#### 🌍 **`GeospatialIndexAttribute`** \n- **Purpose**: Defines a geospatial index on a field or property.\n- **Usage**: Apply this attribute to fields or properties that store geospatial data (e.g., coordinates).\n- **Optional Properties**:\n    - **`Name`**: Specifies the name of the index. If not provided, MongoDB generates a default name.\n    - **`IndexType`**: Specifies the type of geospatial index. Options are `TwoD` (default) or `TwoDSphere`.\n\n##### Example:\n```csharp\npublic class Location : Schema\u003cGuid\u003e\n{\n    [GeospatialIndex(Name = \"location_geo_index\", IndexType = IndexType.TwoDSphere)]\n    public double[] Coordinates { get; set; }\n}\n```\n- **Explanation**: This creates a geospatial index on the `Coordinates` property, using the `TwoDSphere` index type, which is useful for querying geospatial data on a spherical surface.\n  What is the Difference Between TwoD and TwoDSphere? 🌐\n\n  - **`TwoD`**: This index type is used for flat, 2D geospatial data. It is suitable for simple 2D coordinate systems.\n\n  - **`TwoDSphere`**: This index type is used for geospatial data on a spherical surface (e.g., Earth). It supports more complex queries involving distances, intersections, and other spherical calculations.\n\n---\n\n### Summary of Index Types 📊\n\n| Attribute                | Purpose                          | Optional Properties            |\n|--------------------------|----------------------------------|--------------------------------|\n| `SingleFieldIndex`       | Single-field index              | `Name`, `Sparse`, `IndexOrder` |\n| `UniqueIndex`            | Unique index                    | `Name`, `Sparse`               |\n| `CompoundIndex`           | Compound index on multiple fields | `Name`, `IndexOrder`           |\n| `GeospatialIndex`         | Geospatial index                | `Name`, `IndexType`             |\n\n---\n\n### Example Model Class 🧑‍💻\n\nHere’s an example of a model class using all the attributes:\n\n```csharp\npublic class Product : Schema\u003cGuid\u003e\n{\n    [SingleFieldIndex(Name = \"name_index\", IndexOrder = IndexOrder.Ascending)]\n    public string Name { get; set; }\n\n    [UniqueIndex(Name = \"sku_unique_index\", Sparse = true)]\n    public string SKU { get; set; }\n\n    [CompoundIndex(Name = \"price_category_index\", IndexOrder = IndexOrder.Descending)]\n    public decimal Price { get; set; }\n\n    [CompoundIndex(Name = \"price_category_index\", IndexOrder = IndexOrder.Ascending)]\n    public string Category { get; set; }\n\n    [GeospatialIndex(Name = \"location_geo_index\")]\n    public double[] Location { get; set; }\n}\n```\n- **Explanation**: This example demonstrates the use of multiple index types within a single model class. It includes a single-field index, a unique index, a compound index, and a geospatial index.\n\n---\n\nEnjoy using **Meerkat**! 🎉 If you have any questions or feedback, feel free to reach out or contribute to the project. 🚀","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbolorundurowb%2Fmeerkat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbolorundurowb%2Fmeerkat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbolorundurowb%2Fmeerkat/lists"}