{"id":37047772,"url":"https://github.com/dlukez/dataloader-dotnet","last_synced_at":"2026-01-14T05:36:00.781Z","repository":{"id":49743433,"uuid":"74420019","full_name":"dlukez/dataloader-dotnet","owner":"dlukez","description":"DataLoader for .NET","archived":false,"fork":false,"pushed_at":"2018-05-29T09:09:36.000Z","size":398,"stargazers_count":40,"open_issues_count":7,"forks_count":5,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-10-20T07:51:53.780Z","etag":null,"topics":["batch","dataloader","dotnet-core","graphql"],"latest_commit_sha":null,"homepage":null,"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/dlukez.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-11-22T01:01:05.000Z","updated_at":"2024-06-03T09:20:00.000Z","dependencies_parsed_at":"2022-09-14T13:01:33.655Z","dependency_job_id":null,"html_url":"https://github.com/dlukez/dataloader-dotnet","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/dlukez/dataloader-dotnet","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dlukez%2Fdataloader-dotnet","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dlukez%2Fdataloader-dotnet/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dlukez%2Fdataloader-dotnet/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dlukez%2Fdataloader-dotnet/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dlukez","download_url":"https://codeload.github.com/dlukez/dataloader-dotnet/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dlukez%2Fdataloader-dotnet/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28410762,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-14T05:26:33.345Z","status":"ssl_error","status_checked_at":"2026-01-14T05:21:57.251Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: 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":["batch","dataloader","dotnet-core","graphql"],"created_at":"2026-01-14T05:36:00.260Z","updated_at":"2026-01-14T05:36:00.776Z","avatar_url":"https://github.com/dlukez.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# DataLoader for .NET\n\nA port of Facebook's [DataLoader](https://github.com/facebook/dataloader) for .NET.\n\n[![NuGet](https://img.shields.io/nuget/v/DataLoader.svg)](https://nuget.org/packages/DataLoader)\n[![MyGet Pre Release](https://img.shields.io/myget/dlukez/vpre/DataLoader.svg?label=myget)](https://www.myget.org/feed/dlukez/package/nuget/DataLoader)\n[![MyGet Build Status](https://www.myget.org/BuildSource/Badge/dlukez?identifier=265cd302-0184-43af-abc8-6041143cfc91)](https://www.myget.org/feed/dlukez/package/nuget/DataLoader)\n\nThis project began as a solution to the [select N+1 problem](https://github.com/graphql-dotnet/graphql-dotnet/issues/21) for [GraphQL .NET](https://github.com/graphql-dotnet/graphql-dotnet) but was implemented as a standalone package that is completely decoupled from any framework.\n\nIt leverages .NET's async/await feature to enable query batching (a la Facebook's [Dataloader](https://github.com/facebook/dataloader)) that should work out of the box, without requiring significant changes to an existing codebase.\n\nIf anyone finds this useful outside of GraphQL, feel free to drop me a message - I'm interested to know of other potential applications that could be catered to.\n\nCheck out the [sample](https://github.com/dlukez/dataloader-dotnet/tree/master/samples/DataLoader.GraphQL.StarWars) to see it used in a GraphQL implementation.\n\n## Caveats\n\nFacebook's implementation runs in Javascript and takes advantage of the [event loop](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame) to fire any pending requests for ID's collected during the previous frame. Unfortunately, not all .NET applications run in an event loop.\n\nAs such, we have defined a special frame or context to contain our load operations. Whenever we want to use a loader, we should be inside one of these contexts. A simple way to do this is by calling the static `DataLoaderContext.Run` method. This method takes a user-supplied delegate and runs it in within a new context, before actually executing any loaders that were called within it.\n\n## Usage\n\nThere are two ways loaders can be used.\n\n### Method 1: Bound/explicit context (recommended)\n\nWith this approach, loader instances are obtained for a particular context using the context's `GetDataLoader` methods. Along with the user-supplied fetch callback, these methods also take a key for caching and reusing instances.\n\n```csharp\nvar results = await DataLoaderContext.Run(async loadCtx =\u003e\n{\n    // Here we obtain a loader using the context's factory method.\n    var droidLoader = loadCtx.Factory.GetDataLoader\u003cint, Droid\u003e(\"droids\", ids =\u003e\n    {\n        using (var db = new StarWarsContext())\n        {\n            return db.Droid.Where(d =\u003e ids.Contains(d.Id)).ToListAsync();\n        }\n    });\n\n    // Queue up some loads.\n    var task1 = droidLoader.LoadAsync(1);\n    var task2 = droidLoader.LoadAsync(2);\n    var task3 = droidLoader.LoadAsync(3);\n\n    // Await the results... Control is yielded to the framework and the loader is fired.\n    var results = Task.WhenAll(task1, task2, task3);\n\n    // We have the results, but let's load some more! Run ensures that asynchronous\n    // continuations behave like the initial call - ID's should be collected and\n    // fetched as a batch after continuations have run.\n    var task4 = droidLoader.LoadAsync(4);\n    var task5 = droidLoader.LoadAsync(5);\n\n    // Return all our results.\n    return (await Task.WhenAll(task4, task5)).Concat(results);\n));\n```\n\n### Example 2: Unbound/implicit context\n\n```csharp\n// Create a floating/unbound loader that will attach itself to the context\n// that's currently active at the time a load method is called.\nvar personLoader = new DataLoader\u003cint, Person\u003e(ids =\u003e\n{\n    using (var db = new StarWarsContext())\n    {\n        return db.Person.Where(p =\u003e ids.Contains(p.Id)).ToListAsync();\n    }\n});\n\nvar results = await DataLoaderContext.Run(async () =\u003e\n{\n    // We have an implicit context here.\n    Debug.Assert(DataLoaderContext.Current != null);\n\n    // Queue up some person loads.\n    var task1 = personLoader.LoadAsync(1);\n    var task2 = personLoader.LoadAsync(2);\n    var task3 = personLoader.LoadAsync(3);\n\n    // Await the results... Control is yielded to the framework and the loader is fired.\n    var results = await Task.WhenAll(task1, task2, task3);\n\n    // We have the results, but let's load some more! Run ensures that asynchronous\n    // continuations behave like the initial call - ID's should be collected and\n    // fetched as a batch after continuations have run.\n    var task4 = personLoader.LoadAsync(4);\n    var task5 = personLoader.LoadAsync(5);\n\n    // Return all our results.\n    return (await Task.WhenAll(task4, task5)).Concat(results);\n});\n```\n\n## To Do\n\n- [x] Basic support\n- [x] Support async fetching\n- [ ] Cancellation\n- [ ] Benchmarks\n- [ ] Multithreaded performance\n\n## Ideas\n\n- [ ] Single worker thread to service loaders\n- [ ] Sync context to handle async/await in load continuations\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdlukez%2Fdataloader-dotnet","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdlukez%2Fdataloader-dotnet","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdlukez%2Fdataloader-dotnet/lists"}