{"id":21873466,"url":"https://github.com/raidenyn/nhibernate.graphql","last_synced_at":"2025-04-15T01:12:49.004Z","repository":{"id":34037790,"uuid":"165392060","full_name":"raidenyn/NHibernate.GraphQL","owner":"raidenyn","description":"Usefull tools to create GraphQL API with NHibernate as data source","archived":false,"fork":false,"pushed_at":"2022-12-08T03:57:06.000Z","size":80,"stargazers_count":6,"open_issues_count":1,"forks_count":2,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-04-15T01:12:18.883Z","etag":null,"topics":["graphql","nhibernate","tools"],"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/raidenyn.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}},"created_at":"2019-01-12T13:39:42.000Z","updated_at":"2021-02-22T17:25:27.000Z","dependencies_parsed_at":"2022-09-20T16:52:23.683Z","dependency_job_id":null,"html_url":"https://github.com/raidenyn/NHibernate.GraphQL","commit_stats":null,"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raidenyn%2FNHibernate.GraphQL","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raidenyn%2FNHibernate.GraphQL/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raidenyn%2FNHibernate.GraphQL/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/raidenyn%2FNHibernate.GraphQL/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/raidenyn","download_url":"https://codeload.github.com/raidenyn/NHibernate.GraphQL/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248986317,"owners_count":21194025,"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":["graphql","nhibernate","tools"],"created_at":"2024-11-28T07:08:09.735Z","updated_at":"2025-04-15T01:12:48.987Z","avatar_url":"https://github.com/raidenyn.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Tools for GraphQL API implementation based on NHibernate\n\n[![Build status](https://ci.appveyor.com/api/projects/status/a3ym70967jj8m6ne/branch/master?svg=true)](https://ci.appveyor.com/project/raidenyn/nhibernate-graphql/branch/master)\n\n## Main methods\n\n### Select data optimization\n\nGraphQL allows you to optimize the amount of data being sent between server and client sides, but it would be also great to optimize traffic between application and database.\n\nThe method `OptimizeQuery` keeps only explicitly requested fields in a LINQ query:\n\n``` cs\n\nvar query = session.Query\u003cUser\u003e()\n                   .Select(user =\u003e new ExposedUser\n                        {\n                            Login = user.Login,\n                            Name = user.FirstName + user.LastName,\n                            Email = user.Email,\n                            FirstName = user.FirstName\n                        });\n\nquery = query.OptimizeQuery(new []\n    {\n        nameof(ExposedUser.Login),\n        nameof(ExposedUser.Name)\n    });\n\n// Now the query retrieves only Login and Name, but Email and FirstName are skipped.\nvar result = query.ToList(); \n```\n\n\u003e Be aware that this technique decreases traffic between database and application,\n\u003e but increases the variety of sql queries. This may have an impact on the database's\n\u003e ability to cache query plans.\n\n### Creating Connections\n\nGraphQL [suggests](https://graphql.org/learn/pagination/) using Relay-style pagination based on cursors technique.\n\n`ToConnection` and `ToConnectionAsync` methods allow you to create Connection objects easily based on your LINQ query.\n\n``` cs\nvar query = session.Query\u003cUser\u003e()\n                   .Where(user =\u003e Email.Contains(\"@gmail.com\"));\n\nConnection\u003cExposedUser\u003e connection = query.ToConnection(\n    orderBy: user =\u003e user.Id,                        // Order by field\n    select: user =\u003e new ExposedUser                 // Select statement\n    {\n        Login = user.Login,\n        Email = user.Email,\n        FirstName = user.FirstName,\n        Name = user.FirstName + user.LastName\n    },\n    request: new Request\n    {\n        First = 2,       // Count of items in the current result (optional)\n        After = \"MQ==\"   // Cursor value from previous response (optional)\n    });\n\n```\n\n#### Creating connections with sorting by multiple fields\n\nSometimes sorting by ID is not enough. For example, we may want to sort our users by `CreatedAt` field. But the field is not unique and different users can receive the same value for the field. In this case we cannot create a unique cursor based only on `CreatedAt` field. So we have to add some unique field to the sorting to guarantee that our sorting is always consistent. ID is good for this.\n\nWe can use an anonymous type to sort our objects by multiple fields:\n\n``` cs\nvar query = session.Query\u003cUser\u003e()\n                   .Where(user =\u003e Email.Contains(\"@gmail.com\"));\n\nConnection\u003cExposedUser\u003e connection = query.ToConnection(\n    orderBy: user =\u003e { user.CreatedAt, user.Id },   // Order by fields (order of fields is important!)\n    select: user =\u003e new ExposedUser                 // Select statement\n    {\n        Login = user.Login,\n        Email = user.Email,\n        FirstName = user.FirstName,\n        Name = user.FirstName + user.LastName\n    },\n    request: new Request\n    {\n        First = 2,       // Count of items in the current result (optional)\n    });\n\n```\n\n### Define sorting direction\n\nOK, now we can sort users by creation time, but what if we want to do it in descending order? We can use `SortBy.Descending` method for this purpose and separately set sorting direction for each field:\n\n``` cs\nvar query = session.Query\u003cUser\u003e()\n                   .Where(user =\u003e Email.Contains(\"@gmail.com\"));\n\nConnection\u003cExposedUser\u003e connection = query.ToConnection(\n    orderBy: user =\u003e {\n        SortBy.Descending(user.CreatedAt),  // Now `CreatedAt` will be sorted in descending order\n        user.Id\n    },\n    select: user =\u003e new ExposedUser            // Select statement\n    {\n        Login = user.Login,\n        Email = user.Email,\n        FirstName = user.FirstName,\n        Name = user.FirstName + user.LastName\n    },\n    request: new Request\n    {\n        First = 2,       // Count of items in the current result (optional)\n    });\n\n```\n\n### Selecting objects from aggregated ids with Many To One relationship\n\nDataLoader is a nice way to optimize the count of select queries to your database, and it solves the N+1 select problem. But sometimes the amount of requested data might be too large to pass it to your database in one SQL request, since most SQL clients can only support so much data in a single request.\n\nMethods `BulkSelect` and `BulkSelectAsync` allow you to request data from the database and put the result to `IDictionary`:\n\n``` cs\n\n// incoming parameters from DataLoader\nIReadOnlyCollection\u003clong\u003e userIds = new [] { 1, 2, 3, 4, 5 };\n\nIDictionary\u003cint, ExposedUserAddress\u003e dictionary =\n  session.Query\u003cUserAddress\u003e().BulkSelect(\n    // filtering query to retrieve a junction of address and user\n    filter: (addresses, userIdsPart) =\u003e\n        from address in addresses\n        from user in address.Users\n        where userIdsPart.Contains(user.Id)\n        select new { address, user },\n\n    // expression to extract result object from the junction\n    select: item =\u003e new ExposedUserAddress\n        {\n            Zip = item.address.Zip,\n            Street = item.address.Street,\n            House = item.address.House,\n            Text = item.address.Street + \" \" + item.address.House + \", \" + item.address.Zip\n        }\n\n    // expression to extract id value from the junction\n    getId: item =\u003e item.address.Id,\n\n    // required ids\n    ids: userIds);\n\n```\n\n### Selecting objects from aggregated ids with Many To Many relationship\n\nSometimes we need to select aggregated objects with many-to-many relationship. Methods `BulkSelectMany` and `BulkSelectManyAsync` help you with it and create `ILookup` result:\n\n``` cs\n\n// incoming parameters from DataLoader\nIReadOnlyCollection\u003clong\u003e userIds = new [] { 1, 2, 3, 4, 5 };\n\nILookup\u003cint, ExposedUserRole\u003e lookup = \n  session.Query\u003cUserRole\u003e().BulkSelectMany(\n    // filtering query to retrieve a junction of address and user\n    filter: (roles, userIdsPart) =\u003e\n        from role in roles \n        from user in role.Users\n        where userIdsPart.Contains(user.Id)\n        select new { role, user },\n\n    // expression to extract result object from the junction\n    select: junction =\u003e new ExposedUserRole\n        {\n            Code = junction.role.Code,\n            Name = junction.role.Name,\n        }\n\n    // expression to extract id value from the junction\n    getResultId: junction =\u003e junction.role.Id,\n\n    // expression to extract id value of joined object from the junction\n    getJoinedId: junction =\u003e junction.user.Id,\n\n    // required ids\n    ids: userIds);\n\n```\n\n## Roadmap\n\n- [ ] Add how it works explanations and documentation\n- [ ] Create more test cases\n- [ ] Support bidirectional connections\n- [ ] Add cache for expression generation\n- [ ] Support splitting by id strategy in bulk selection\n- [ ] Support connections with bulk selection\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fraidenyn%2Fnhibernate.graphql","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fraidenyn%2Fnhibernate.graphql","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fraidenyn%2Fnhibernate.graphql/lists"}