=============== LIBRARY RULES =============== From library maintainers: - Set QueryExecutionMode.Ssr in AddDotNetQuery for Blazor Server / SSR so IQueryClient is scoped per circuit; the default Csr registers it as a singleton. - Query TData must be a reference type; wrap value types in a record or class. - Dispose queries and view models when their owner is torn down: a live subscription pins the cache entry indefinitely. - In MVVM, use ObserveOnUi() for the Success/Failure/Settled event streams; the bindable view model properties coalesce latest-wins and would drop events. - Call MutationViewModel.ToCommand once per command, typically in a constructor, never per list row. - In tests, register a TestScheduler as the IScheduler to drive stale time, cache eviction, and refetch intervals in virtual time. ### Install DotNetQuery.Blazor package Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/blazor.md Installs the DotNetQuery.Blazor package for Blazor components. ```bash dotnet add package DotNetQuery.Blazor ``` -------------------------------- ### Install DotNetQuery NuGet packages Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/getting-started.md Install the required NuGet packages. The core library is always required; the other packages are optional depending on your project type (DI, Blazor, Blazor DevTools, or MVVM). ```bash # Core library — always required dotnet add package DotNetQuery.Core # DI integration — if you use Microsoft.Extensions.DependencyInjection dotnet add package DotNetQuery.Extensions.DependencyInjection # Blazor components — if you use Blazor dotnet add package DotNetQuery.Blazor # Blazor DevTools — live cache inspector for development dotnet add package DotNetQuery.Blazor.DevTools # MVVM view models — if you use MAUI, WPF, WinUI, UNO Platform, or Avalonia dotnet add package DotNetQuery.Mvvm ``` -------------------------------- ### Install DotNetQuery.Mvvm package Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/mvvm.md Adds the DotNetQuery.Mvvm package to the project. The package depends only on DotNetQuery.Core, with no MVVM toolkit or platform frameworks. ```bash dotnet add package DotNetQuery.Mvvm ``` -------------------------------- ### Install DotNetQuery.Blazor.DevTools package Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/devtools.md Adds the DevTools NuGet package to the Blazor project. ```bash dotnet add package DotNetQuery.Blazor.DevTools ``` -------------------------------- ### QueryOptions configuration example Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Shows how to create a QueryOptions instance with required KeyFactory and Fetcher, plus optional overrides like StaleTime, CacheTime, RefetchInterval, RetryHandler, IsEnabled, InitialData, and DataComparer. ```csharp new QueryOptions { // Required KeyFactory = id => QueryKey.From("users", id), Fetcher = (id, ct) => httpClient.GetFromJsonAsync($"/api/users/{id}", ct)!, // Optional overrides (null = use global default) StaleTime = TimeSpan.FromMinutes(5), CacheTime = TimeSpan.FromMinutes(30), RefetchInterval = TimeSpan.FromSeconds(30), // poll while subscribers exist RetryHandler = new MyRetryHandler(), IsEnabled = true, // default; set false for conditional/lazy queries InitialData = null, // pre-populate cache before first fetch; always treated as stale // Comparer to suppress re-emissions when fetched data is structurally identical DataComparer = EqualityComparer.Default, } ``` -------------------------------- ### InfiniteQueryOptions configuration example Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Configures an infinite query with required and optional options, including key factory, fetcher, initial page param, and next page param logic. ```csharp new InfiniteQueryOptions, int> { // Required KeyFactory = id => ["items", id], Fetcher = (id, page, ct) => api.GetItemsAsync(id, page, ct), InitialPageParam = 1, GetNextPageParam = info => info.Page.Count < PageSize ? PageParam.None : PageParam.Some(info.PageParam + 1), // Optional GetPreviousPageParam = null, // omit for forward-only lists MaxPages = null, // unbounded by default; set to trim oldest pages StaleTime = null, CacheTime = null, RefetchInterval = null, RetryHandler = null, IsEnabled = true, DataComparer = null, InitialData = null, // pre-seed first page; query starts Success, refetches in background } ``` -------------------------------- ### Install DotNetQuery packages with dotnet add package Source: https://github.com/psachmann/dotnet-query/blob/main/README.md Use these commands to install the DotNetQuery packages. The core library is always required; the others are optional depending on your project type. The Blazor DevTools package is intended for development use as a live cache inspector. ```bash dotnet add package DotNetQuery.Core dotnet add package DotNetQuery.Extensions.DependencyInjection dotnet add package DotNetQuery.Blazor dotnet add package DotNetQuery.Blazor.DevTools dotnet add package DotNetQuery.Mvvm ``` -------------------------------- ### QueryKey.From examples for cache keys Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/introduction.md Shows how to create query keys with one or more parts. Keys are immutable and equality-comparable, and two queries with the same key share a single fetch and cache entry. ```csharp QueryKey.From("users") // all users QueryKey.From("users", 42) // user with id 42 QueryKey.From("users", 42, "posts") // posts for user 42 ``` -------------------------------- ### Register DotNetQuery with DI (minimal) Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/getting-started.md Registers the IQueryClient with the DI container using default options. This is the minimal setup for applications using Microsoft.Extensions.DependencyInjection. ```csharp builder.Services.AddDotNetQuery(); ``` -------------------------------- ### InfiniteTransition component example Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt For IInfiniteQuery. Shows Content whenever HasData, including during refetches and FetchNextPage. Shows Loading only before first page. ```razor @foreach (var item in state.Pages.SelectMany(p => p)) {
@item.Name
} @if (state.HasNextPage) { }

Loading…

Error: @e.Message

``` -------------------------------- ### Wrap an IInfiniteQuery into an InfiniteQueryViewModel Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/mvvm.md Shows how to wrap an IInfiniteQuery into an InfiniteQueryViewModel for use in a view model. The example uses PostsService.PostsQuery.ToViewModel() and exposes the view model as a property, with Load(Guid) setting the query arguments and Dispose() disposing the wrapped query. ```csharp public sealed class PostsPageViewModel : IDisposable { public PostsPageViewModel(PostsService posts) { Posts = posts.PostsQuery.ToViewModel(); } public InfiniteQueryViewModel, int> Posts { get; } public void Load(Guid boardId) => Posts.SetArgs(boardId); public void Dispose() => Posts.Dispose(); } ``` -------------------------------- ### MutationOptions configuration example Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Shows how to configure a mutation with a required Mutator delegate and optional settings like RetryHandler, IsEnabled, InvalidateKeys, and lifecycle callbacks. Execution order is OnMutate → mutator → InvalidateKeys → OnSuccess/OnFailure → OnSettled. InvalidateKeys fires immediately when the mutator's Task resolves; for eventually-consistent backends, use OnSuccess and call queryClient.Invalidate manually. ```csharp new MutationOptions { // Required Mutator = (request, ct) => httpClient.PostAsJsonAsync("/api/users", request, ct), // Optional RetryHandler = null, // null = use global default IsEnabled = true, InvalidateKeys = [QueryKey.From("users")], // invalidated automatically on success // Lifecycle callbacks — execution order: OnMutate → mutator → InvalidateKeys → OnSuccess/OnFailure → OnSettled OnMutate = args => { /* snapshot + apply optimistic update */ }, OnSuccess = (args, result) => toast.Show($"Created {result.Name}"), OnFailure = error => toast.Show($"Error: {error.Message}"), OnSettled = () => isBusy = false, // fires for success, failure, AND cancellation } ``` -------------------------------- ### InfiniteSuspense component example Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt For IInfiniteQuery. Shows Loading during initial load and full refetches, Content when data is available. Content receives InfiniteQueryState to render pages and load more. ```razor @foreach (var item in state.Pages.SelectMany(p => p)) {
@item.Name
} @if (state.HasNextPage) { }

Loading…

Error: @e.Message

``` -------------------------------- ### Creating commands with ToCommand for per-row and page-level args Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Shows two ways to build commands from a MutationViewModel. The first creates a per-row command using a shared instance with a parameter mapping to TArgs; calling ToCommand per row would leak. The second creates a command that gets args from the page VM's state instead of the command parameter. ```csharp // Per-row command: parameter maps to TArgs, one shared instance handed to every row // (calling ToCommand per row would leak -- see above). public ICommand ToggleCommand { get; } = ToggleItem.ToCommand(item => new ToggleArgs(item.Id)); // Args come from the page VM's own state instead of the command parameter. public ICommand AddItemCommand { get; } = AddItem.ToCommand(() => new AddItemArgs(NewItemText)); ``` -------------------------------- ### Prefetch multiple keys in parallel Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/prefetching.md Warms the cache for several keys at once using Task.WhenAll. Each key gets its own cache entry with no cross-contamination. ```csharp // Warm the first page of results in parallel await Task.WhenAll( facade.UserQuery.PrefetchAsync(1), facade.UserQuery.PrefetchAsync(2), facade.UserQuery.PrefetchAsync(3) ); ``` -------------------------------- ### PageParam usage examples Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Shows how to signal no more pages, provide a next page parameter, and use implicit conversion from the underlying type. ```csharp // Signal "no more pages" PageParam.None // Signal "next page param is 2" PageParam.Some(2) // Implicit conversion from the underlying type PageParam p = 2; // equivalent to Some(2) ``` -------------------------------- ### Trigger a fetch with SetArgs Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/getting-started.md Starts fetching data for user 42 by pushing arguments to the query. The query does not fetch until SetArgs is called. ```csharp // Trigger a fetch for user 42 userQueries.UserQuery.SetArgs(42); ``` -------------------------------- ### IQueryClient.CreateQuery Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Gets or creates a query. Queries with the same key share one cache entry. ```APIDOC ## IQueryClient.CreateQuery ### Description Gets or creates a query. Queries with the same key share one cache entry. ### Method CreateQuery ### Signature IQuery CreateQuery(QueryOptions options) ### Parameters - **options** (QueryOptions) - Required - The query options. ### Returns - **IQuery** - The query instance. ``` -------------------------------- ### Suspense component example Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Shows loading, content, and failure states for a query. Content receives the query result, Loading is optional, Failure is optional. ```razor

@user.Name

Loading...

Error: @error.Message

``` -------------------------------- ### Typical usage - C# view model Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Example of a page view model using QueryViewModel with IQueryClient injected via DI. The constructor creates a QueryViewModel with options for key factory, fetcher, and stale time. Load sets the arguments, Dispose disposes the view model. ```csharp public sealed class UserPageViewModel : IDisposable { public UserPageViewModel(IQueryClient queryClient, IUserApi api) => User = new QueryViewModel(queryClient, new QueryOptions { KeyFactory = id => QueryKey.From("users", id), Fetcher = (id, ct) => api.GetUserAsync(id, ct), StaleTime = TimeSpan.FromMinutes(5), }); public QueryViewModel User { get; } public void Load(int userId) => User.SetArgs(userId); public void Dispose() => User.Dispose(); } ``` -------------------------------- ### Set Fetcher for Infinite Query Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/infinite-queries.md Defines the fetcher that receives the args, the current page param, and a CancellationToken. This example fetches a page of posts. ```csharp Fetcher = (listId, page, ct) => api.GetPostsAsync(listId, page, PageSize, ct) ``` -------------------------------- ### Create Infinite Query with InitialData Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/infinite-queries.md Creates an infinite query with InitialData pre-seeding the first page from a local cache or SSR payload. The query starts in Success state with no loading flash and immediately triggers a background refetch. Only the first page is seeded; subsequent pages must be fetched via FetchNextPage. The seeded page uses InitialPageParam as its page parameter. ```csharp public readonly IInfiniteQuery, int> PostsQuery = queryClient.CreateInfiniteQuery(new InfiniteQueryOptions, int> { KeyFactory = boardId => ["posts", boardId], Fetcher = (boardId, page, ct) => api.GetPostsAsync(boardId, page, PageSize, ct), InitialPageParam = 1, GetNextPageParam = info => info.Page.Count < PageSize ? PageParam.None : PageParam.Some(info.PageParam + 1), InitialData = cachedFirstPage, // IReadOnlyList from local storage / SSR }); ``` -------------------------------- ### SyncFrom() usage examples Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Shows two calls: one for same-type collections using a key selector, and one for projection where rows are view models built from models. The projection overload refreshes matched rows via update and never replaces them. ```csharp Lists.SyncFrom(freshLists, list => list.Id); // same-type: TodoList is a plain record ItemRows.SyncFrom( // projection: build/refresh row view models freshItems, model => model.Id, row => row.Model.Id, model => new TodoItemViewModel(model), (row, model) => row.Update(model)); ``` -------------------------------- ### Blazor component with queries and mutations Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/blazor.md Complete Blazor component example showing queries and mutations with Blazor components. Queries are defined in an injected service class; the component only handles rendering and pushing args. The C# service defines two queries (UserQuery and PostsQuery) with key factories, fetchers, and a stale time. The Razor component uses Transition and Suspense components to render loading and failure states, and sets query args in OnParametersSet. ```csharp public sealed class UserProfileQueries(IQueryClient queryClient, HttpClient http) : IDisposable { public readonly IQuery UserQuery = queryClient.CreateQuery( new QueryOptions { KeyFactory = id => QueryKey.From("users", id), Fetcher = (id, ct) => http.GetFromJsonAsync($"/api/users/{id}", ct)!, StaleTime = TimeSpan.FromMinutes(5), }); public readonly IQuery> PostsQuery = queryClient.CreateQuery( new QueryOptions> { KeyFactory = id => QueryKey.From("users", id, "posts"), Fetcher = (id, ct) => http.GetFromJsonAsync>($"/api/users/{id}/posts", ct)!, }); public void Dispose() { UserQuery.Dispose(); PostsQuery.Dispose(); } } ``` ```html @page "/users/{Id:int}" @inject UserProfileQueries Queries

@user.Name

    @foreach (var post in posts) {
  • @post.Title
  • }

Loading posts...

Loading user...

Error: @error.Message

@code { [Parameter] public int Id { get; set; } protected override void OnParametersSet() { Queries.UserQuery.SetArgs(Id); Queries.PostsQuery.SetArgs(Id); } } ``` -------------------------------- ### OnSuccess with Invalidate for eventual consistency Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/mutations.md Use OnSuccess to control when invalidation happens, recommended for eventually-consistent backends where immediate invalidation may return stale data. This example invalidates the users key after the mutation succeeds. ```csharp OnSuccess = (req, _) => queryClient.Invalidate(QueryKey.From("users")) ``` -------------------------------- ### SyncFrom usage for sidebar list and row view models Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/mvvm.md Usage examples for the two SyncFrom overloads. The first reconciles a sidebar list of plain records using the same-type overload. The second uses the projection overload to build TodoItemViewModel rows from TodoItem models and refresh existing rows in place via update. ```csharp Lists.SyncFrom(freshLists, list => list.Id); ``` ```csharp ItemRows.SyncFrom( freshItems, model => model.Id, row => row.Model.Id, model => new TodoItemViewModel(model), (row, model) => row.Update(model)); ``` -------------------------------- ### Optimistic updates in a TodoFacade with IQueryClient and IMutation Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/optimistic-updates.md Full example of optimistic updates in a facade. The facade owns both the query and the mutation, giving it access to both in the same closure. On mutate, it snapshots current data, cancels in-flight fetches, and applies the optimistic update; on failure it rolls back via SetData; on success InvalidateKeys triggers a background refetch. ```csharp public class TodoFacade { private readonly ITodoApi _api; private TodoDto? _snapshot; public IQuery TodoQuery { get; } public IMutation UpdateTodo { get; } public TodoFacade(IQueryClient client, ITodoApi api) { _api = api; TodoQuery = client.CreateQuery(new QueryOptions { KeyFactory = id => QueryKey.From("todos", id), Fetcher = (id, ct) => _api.GetTodoAsync(id, ct), StaleTime = TimeSpan.FromMinutes(5), }); UpdateTodo = client.CreateMutation(new MutationOptions { Mutator = (todo, ct) => _api.UpdateTodoAsync(todo, ct), OnMutate = todo => { _snapshot = TodoQuery.CurrentState.CurrentData; // save for rollback TodoQuery.Cancel(); // cancel any in-flight fetch TodoQuery.SetData(todo); // apply optimistic update }, OnFailure = _ => { if (_snapshot is not null) TodoQuery.SetData(_snapshot); // roll back }, // Replace optimistic data with the real server response InvalidateKeys = [QueryKey.From("todos", ...)], }); } } ``` -------------------------------- ### Creating a client without DI Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Shows how to instantiate IQueryClient using QueryClientFactory.Create with optional scheduler and logger parameters. Defaults to DefaultScheduler.Instance and NullLogger when null. ```csharp IQueryClient client = QueryClientFactory.Create( new QueryClientOptions { StaleTime = TimeSpan.FromMinutes(1) }, scheduler: null, // defaults to DefaultScheduler.Instance logger: null // defaults to NullLogger ); ``` -------------------------------- ### Register DotNet Query and create a query Source: https://github.com/psachmann/dotnet-query/blob/main/docs/index.md Shows how to register DotNet Query in Program.cs with a stale time of one minute, then create a query with a key factory and fetcher, set arguments, and subscribe to success and failure observables. ```csharp // Register in Program.cs builder.Services.AddDotNetQuery(options => { options.StaleTime = TimeSpan.FromMinutes(1); }); // In your service or component var query = queryClient.CreateQuery(new QueryOptions { KeyFactory = id => QueryKey.From("users", id), Fetcher = (id, ct) => userService.GetByIdAsync(id, ct), }); query.SetArgs(42); query.Success.Subscribe(user => Console.WriteLine($"Hello, {user.Name}!")); query.Failure.Subscribe(error => Console.WriteLine($"Oops: {error.Message}")); ``` -------------------------------- ### IQueryClient.CreateInfiniteQuery Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Gets or creates an infinite (paginated) query. Queries with the same key share one cache entry. ```APIDOC ## IQueryClient.CreateInfiniteQuery ### Description Gets or creates an infinite (paginated) query. Queries with the same key share one cache entry. ### Method CreateInfiniteQuery ### Signature IInfiniteQuery CreateInfiniteQuery(InfiniteQueryOptions options) ### Parameters - **options** (InfiniteQueryOptions) - Required - The infinite query options. ### Returns - **IInfiniteQuery** - The infinite query instance. ``` -------------------------------- ### Create a query with IQueryClient.CreateQuery Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/queries.md Creates a query with the two required properties, KeyFactory and Fetcher. All other options are optional and override global defaults from QueryClientOptions. ```csharp IQuery query = queryClient.CreateQuery(new QueryOptions { KeyFactory = args => QueryKey.From(...), Fetcher = (args, ct) => ..., }); ``` -------------------------------- ### QueryKey creation and Default sentinel Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Shows how to create QueryKey instances with one or more parts, and the Default sentinel returned by IQuery.Key before SetArgs is called. ToString() joins parts with ':'. ```csharp // Create keys QueryKey.From("users") // simple QueryKey.From("users", 42) // parameterized — different entry per id QueryKey.From("users", 42, "posts") // compound/hierarchical // Sentinel for the uninitialized state QueryKey.Default // returned by IQuery.Key before SetArgs is called ``` -------------------------------- ### Transition component example Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Shows stale-while-revalidate behavior: keeps old content during background refetches, only shows Loading when no previous data. ```razor @foreach (var item in items) { }

Loading...

@error.Message

``` -------------------------------- ### Set args for initial load Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/infinite-queries.md Provides the args for the infinite query. The first fetch uses InitialPageParam. ```csharp query.SetArgs(listId); ``` -------------------------------- ### Add all DotNetQuery packages Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/migrating-to-v2.md Run these dotnet add package commands to upgrade all DotNetQuery packages to matching versions. Execute them together to ensure version compatibility. ```bash dotnet add package DotNetQuery.Core dotnet add package DotNetQuery.Extensions.DependencyInjection dotnet add package DotNetQuery.Blazor dotnet add package DotNetQuery.Blazor.DevTools dotnet add package DotNetQuery.Mvvm ``` -------------------------------- ### Create IQueryClient with QueryClientFactory Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/getting-started.md Creates an IQueryClient without a DI container. Remember to call client.Dispose() when done. ```csharp IQueryClient client = QueryClientFactory.Create(new QueryClientOptions { StaleTime = TimeSpan.FromMinutes(1), }); ``` -------------------------------- ### Conditional Queries with IsEnabled Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Creates a query that starts disabled and can be enabled later. Shows how to set IsEnabled to false and then enable it when a user selects an ID. ```csharp var query = queryClient.CreateQuery(new QueryOptions { KeyFactory = id => QueryKey.From("users", id), Fetcher = (id, ct) => api.GetUserAsync(id!.Value, ct), IsEnabled = false, // start disabled }); // Later, when user selects an ID: query.SetEnabled(true); query.SetArgs(selectedUserId); ``` -------------------------------- ### UserFacade with PrefetchAsync Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/prefetching.md Defines a facade that owns a query and exposes PrefetchAsync. Inject this facade into components instead of IQueryClient. The query uses a KeyFactory, Fetcher, and StaleTime of 5 minutes. ```csharp public class UserFacade { private readonly IUserApi _api; public IQuery UserQuery { get; } public UserFacade(IQueryClient client, IUserApi api) { _api = api; UserQuery = client.CreateQuery(new QueryOptions { KeyFactory = id => QueryKey.From("users", id), Fetcher = (id, ct) => _api.GetUserAsync(id, ct), StaleTime = TimeSpan.FromMinutes(5), }); } } ``` -------------------------------- ### Conditional query with SetEnabled and SetArgs Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/queries.md Conditional query pattern: start disabled and enable when a valid ID is available. Uses IsEnabled in QueryOptions and SetEnabled/SetArgs later. ```csharp // Start disabled; enable when we have a valid ID var query = queryClient.CreateQuery(new QueryOptions { KeyFactory = id => QueryKey.From("users", id), Fetcher = (id, ct) => ..., IsEnabled = false, }); // Later, when the user selects an ID: query.SetEnabled(true); query.SetArgs(selectedUserId); ``` -------------------------------- ### Subscribe to shortcut streams (Success, Failure, Settled) Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/mutations.md Subscribe to the shortcut streams for specific outcomes: Success provides the data, Failure provides the error, and Settled fires when the mutation settles. These are convenient for handling each outcome separately. ```csharp mutation.Success.Subscribe(data => NavigateTo($"/users/{data.Id}")); mutation.Failure.Subscribe(error => ShowError(error.Message)); mutation.Settled.Subscribe(_ => HideSpinner()); ``` -------------------------------- ### Create a query with QueryOptions and subscribe to Success/Failure Source: https://github.com/psachmann/dotnet-query/blob/main/README.md Creates a query using QueryOptions with a KeyFactory and Fetcher. Set arguments with SetArgs and subscribe to Success and Failure observables to handle results. ```csharp // Create a query var query = queryClient.CreateQuery(new QueryOptions { KeyFactory = id => QueryKey.From("users", id), Fetcher = (id, ct) => userService.GetByIdAsync(id, ct), }); query.SetArgs(42); query.Success.Subscribe(user => Console.WriteLine($"Hello, {user.Name}!")); query.Failure.Subscribe(error => Console.WriteLine($"Oops: {error.Message}")); ``` -------------------------------- ### Service/Facade class with UserService Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Defines a UserService class that encapsulates queries and mutations. It registers with DI and injects IQueryClient and HttpClient. The service exposes a UserQuery and UpdateUser mutation, and implements IDisposable to dispose them. ```csharp public sealed class UserService(IQueryClient queryClient, HttpClient http) : IDisposable { public readonly IQuery UserQuery = queryClient.CreateQuery( new QueryOptions { KeyFactory = id => QueryKey.From("users", id), Fetcher = (id, ct) => http.GetFromJsonAsync($"/api/users/{id}", ct)!, StaleTime = TimeSpan.FromMinutes(5), }); public readonly IMutation UpdateUser = queryClient.CreateMutation(new MutationOptions { Mutator = (req, ct) => http.PutAsJsonAsync($"/api/users/{req.Id}", req, ct), InvalidateKeys = [QueryKey.From("users")], }); public void Dispose() { UserQuery.Dispose(); UpdateUser.Dispose(); } } ``` -------------------------------- ### Invalidate by Predicate Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/caching.md Invalidates all entries whose key string starts with 'users', or all entries when using a predicate that always returns true. Useful after mutations affecting multiple queries. ```csharp // Invalidate all user-related entries queryClient.Invalidate(key => key.ToString().StartsWith("users")); // Invalidate everything queryClient.Invalidate(_ => true); ``` -------------------------------- ### ObserveOnUi usage in MainViewModel Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/mvvm.md Example usage of ObserveOnUi in a ViewModel constructor. It subscribes to the Success event of a mutation and observes each emission on the UI thread. Every emission is observed, so no Success event is dropped. ```csharp public MainViewModel(TodosMutations mutations) { // Remember the newly created list's id so the refreshed sidebar can select it. // Every Success emission must be observed -- missing one would leave the wrong list selected. mutations.CreateTodoList.Success .ObserveOnUi() .Subscribe(list => _pendingSelectionId = list.Id); } ``` -------------------------------- ### QueryClientFactory.Create Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Creates an IQueryClient instance without dependency injection. ```APIDOC ## QueryClientFactory.Create ### Description Creates an IQueryClient instance without dependency injection. ### Method Create ### Signature IQueryClient Create(QueryClientOptions options, Scheduler scheduler = null, Logger logger = null) ### Parameters - **options** (QueryClientOptions) - Required - The client options. - **scheduler** (Scheduler) - Optional - The scheduler to use; defaults to DefaultScheduler.Instance. - **logger** (Logger) - Optional - The logger to use; defaults to NullLogger. ### Returns - **IQueryClient** - The created client. ``` -------------------------------- ### Invalidate infinite queries via client Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/infinite-queries.md Client-level invalidation targets infinite queries the same way as regular queries. The first example invalidates by exact key, the second by a predicate on the key string. ```csharp queryClient.Invalidate(["posts", listId]); queryClient.Invalidate(key => key.ToString().StartsWith("posts")); ``` -------------------------------- ### Comparing against QueryKey.Default sentinel Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/migrating-to-v2.md Shows how to detect an uninitialised query by comparing against QueryKey.Default instead of QueryKey.From("\0"). ```csharp if (query.Key == QueryKey.Default) { // SetArgs has not been called yet } ``` -------------------------------- ### Get inspector from query client Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/cache-inspector.md Attempts to retrieve an IQueryCacheInspector from a query client. Returns early if the client does not support inspection, such as a custom IQueryClient implementation or a mock. Clients from QueryClientFactory.Create or AddDotNetQuery() always support it. ```csharp using DotNetQuery.Core; using DotNetQuery.Core.Inspection; if (!queryClient.TryGetInspector(out var inspector)) { return; // a custom IQueryClient implementation (or a mock) that does not support inspection } ``` -------------------------------- ### ObserveOnUi() usage example Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Subscribes to the Success event of a mutation and assigns the created list's ID to a pending selection field. This ensures every emission is delivered, unlike the coalesced state properties. ```csharp mutations.CreateTodoList.Success.ObserveOnUi().Subscribe(list => _pendingSelectionId = list.Id); ``` -------------------------------- ### Create ICommand with ToCommand overloads Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/mvvm.md Builds ICommand instances from a MutationViewModel using ToCommand. The first overload maps the command parameter to TArgs for per-row commands; the second uses a factory and an optional canExecute predicate for page-level commands. Both share ExecuteCommand's double-submit protection and return an IMutationCommand. ```csharp // The command parameter maps to TArgs. Use for commands bound per-row in a list, // with CommandParameter carrying the row's data. public ICommand ToggleCommand { get; } = ToggleItem.ToCommand(item => new ToggleArgs(item.Id)); // Args come from the page view model instead of the command parameter. // Use for a single button whose input is a bound field elsewhere on the page. public ICommand AddItemCommand { get; } = AddItem.ToCommand(() => new AddItemArgs(NewItemText), canExecute: () => NewItemText.Length > 0); ``` -------------------------------- ### Removing existing IQueryClient registration before AddDotNetQuery Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/migrating-to-v2.md Shows how to replace an existing IQueryClient registration in a test host by removing it first, because AddDotNetQuery now uses TryAdd and will not override. ```csharp using Microsoft.Extensions.DependencyInjection.Extensions; services.RemoveAll(); services.AddDotNetQuery(options => options.StaleTime = TimeSpan.Zero); ``` -------------------------------- ### Create an Infinite Query with IQueryClient.CreateInfiniteQuery Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/infinite-queries.md Creates an infinite query that accumulates pages of data. TData is the type of a single page (e.g., List), and TPageParam is the type of the cursor or page identifier (e.g., int, Guid, DateTimeOffset). ```csharp IInfiniteQuery query = queryClient.CreateInfiniteQuery(new InfiniteQueryOptions { KeyFactory = args => ..., Fetcher = (args, page, ct) => ..., InitialPageParam = ..., GetNextPageParam = info => ..., }); ``` -------------------------------- ### Subscribe to OpenTelemetry with QueryTelemetry.SourceName Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Shows how to configure OpenTelemetry in the application's service collection to capture tracing and metrics from the dotnet-query library. Uses the BCL ActivitySource and Meter with the source name "DotNetQuery". ```csharp builder.Services.AddOpenTelemetry() .WithTracing(b => b.AddSource(QueryTelemetry.SourceName)) // "DotNetQuery" .WithMetrics(b => b.AddMeter(QueryTelemetry.SourceName)); ``` -------------------------------- ### Subscribe to Success, Failure, and Settled shortcut streams Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/queries.md Use shortcut streams to receive only the relevant payload: Success emits unwrapped TData, Failure emits the Exception, and Settled emits the final QueryState after every fetch. These are useful for rendering data, showing errors, and hiding spinners regardless of outcome. ```csharp // Emits only the unwrapped TData on each successful fetch query.Success.Subscribe(data => Render(data)); // Emits only the Exception on each failed fetch query.Failure.Subscribe(error => ShowError(error)); // Emits the final QueryState after every fetch (success or failure) // Useful for hiding spinners regardless of outcome query.Settled.Subscribe(_ => HideSpinner()); ``` -------------------------------- ### Execute a mutation with Execute method Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/mutations.md Call Execute with the args to trigger the mutation. If a previous execution is still running, it is cancelled and the new one starts immediately. If the mutation is disabled (IsEnabled = false or SetEnabled(false) was called), Execute is silently ignored. ```csharp mutation.Execute(new CreateUserRequest { Name = "Alice" }); ``` -------------------------------- ### Optimistic update with OnMutate and OnFailure snapshot restore Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/mutations.md OnMutate snapshots current state, cancels in-flight fetch, and writes predicted data via SetData. OnFailure restores the snapshot if the mutation fails. See the Optimistic Updates guide for the full pattern. ```csharp UserDto? _snapshot = null; OnMutate = user => { _snapshot = UserQuery.CurrentState.CurrentData; UserQuery.Cancel(); UserQuery.SetData(user); }, OnFailure = _ => { if (_snapshot is not null) { UserQuery.SetData(_snapshot); } }, ``` -------------------------------- ### Override retry handler per query or mutation Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/retries.md Override the retry handler for a specific query or mutation without changing the global config. The example shows a query using NoRetryHandler to fail fast and a mutation using LinearRetryHandler with 3 attempts and a 2-second delay. ```csharp // This query uses no retry — fail fast new QueryOptions { KeyFactory = id => QueryKey.From("users", id), Fetcher = (id, ct) => ..., RetryHandler = new NoRetryHandler(), } // This mutation uses a custom policy new MutationOptions { Mutator = (req, ct) => ..., RetryHandler = new LinearRetryHandler(attempts: 3, delay: TimeSpan.FromSeconds(2)), } ``` -------------------------------- ### C# service with IInfiniteQuery Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Defines an ItemService that creates an infinite query with pagination. Uses IQueryClient and IApi. The query fetches items page by page, with a page size of 20, and determines next page based on whether the current page is full. ```csharp public sealed class ItemService(IQueryClient client, IApi api) : IDisposable { private const int PageSize = 20; public readonly IInfiniteQuery, int> ItemsQuery = client.CreateInfiniteQuery(new InfiniteQueryOptions, int> { KeyFactory = listId => ["items", listId], Fetcher = (listId, page, ct) => api.GetItemsAsync(listId, page, PageSize, ct), InitialPageParam = 1, GetNextPageParam = info => info.Page.Count < PageSize ? PageParam.None : PageParam.Some(info.PageParam + 1), }); public void Dispose() => ItemsQuery.Dispose(); } ``` -------------------------------- ### Invalidate() - Mark data as stale Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/queries.md Marks cached data as stale. With active subscribers, a fetch starts immediately; without subscribers, the fetch is deferred until the next subscriber joins. Respects stale time — if data was fetched within the stale-time window, it is a no-op. ```csharp query.Invalidate(); ``` -------------------------------- ### Create a query with key factory and fetcher Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/getting-started.md Defines a query with a key factory and fetcher. The fetcher throws InvalidOperationException when the user is not found. The query must be disposed to release resources. ```csharp public sealed class UserQueries(IQueryClient queryClient, HttpClient httpClient) : IDisposable { public readonly IQuery UserQuery = queryClient.CreateQuery( new QueryOptions { KeyFactory = id => QueryKey.From("users", id), Fetcher = async (id, ct) => await httpClient.GetFromJsonAsync($"/api/users/{id}", ct) ?? throw new InvalidOperationException("User not found."), } ); public void Dispose() => UserQuery.Dispose(); } ``` -------------------------------- ### View model constructors: owning vs wrapping Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/mvvm.md Shows the two constructor forms for view models: owning (creates the query/mutation and disposes it) and wrapping (does not own the original). Use the owning form for page view models receiving IQueryClient via DI; use the wrapping form when a shared service owns long-lived queries and mutations. ```csharp // 1. The view model CREATES the query/mutation and owns it: Dispose() disposes it too. // Recommended for page view models receiving IQueryClient via DI. new QueryViewModel(queryClient, options); new InfiniteQueryViewModel(queryClient, options); new MutationViewModel(queryClient, options); // 2. The view model WRAPS an existing query/mutation and does not own it: // Dispose() releases only the view model's subscription; you dispose the original. // Use when a shared service owns long-lived queries and mutations. new QueryViewModel(existingQuery); new InfiniteQueryViewModel(existingQuery); new MutationViewModel(existingMutation); ``` -------------------------------- ### Subscribe to full state stream with QueryState Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/queries.md Subscribe to the full state stream to react to every transition. New subscribers immediately receive the current state due to replay semantics. Handle each QueryStatus to show spinners, render data, or display errors. ```csharp query.State.Subscribe(state => { switch (state.Status) { case QueryStatus.Idle: // nothing loaded yet break; case QueryStatus.Fetching: ShowSpinner(); break; case QueryStatus.Success: Render(state.CurrentData!); break; case QueryStatus.Failure: ShowError(state.Error!); break; } }); ``` -------------------------------- ### Wrapping multiple queries and mutations with CompositeDisposable Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/mvvm.md Example page view model that wraps multiple queries and mutations using ToViewModel with a shared CompositeDisposable. Disposing the page disposes all wrapped view models in one call, while the original queries and mutations remain owned by the service. ```csharp public sealed class TodoPageViewModel : IDisposable { private readonly CompositeDisposable _disposables = []; public TodoPageViewModel(TodosQueries queries, TodosMutations mutations) { List = queries.TodoListQuery.ToViewModel(_disposables); AddItem = mutations.AddTodoItem.ToViewModel(_disposables); } public QueryViewModel List { get; } public MutationViewModel AddItem { get; } public void Dispose() => _disposables.Dispose(); // releases both view models; TodosQueries/TodosMutations still own the originals } ``` -------------------------------- ### Invalidate cached queries via IQueryClient Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Shows three ways to invalidate cached queries: by exact key, by predicate on key string, or by invalidating all. The first uses QueryKey.From with a specific key, the second uses a predicate that checks if the key string starts with 'users', and the third invalidates everything. ```csharp client.Invalidate(QueryKey.From("users", 42)); // exact key match client.Invalidate(key => key.ToString().StartsWith("users")); // predicate client.Invalidate(_ => true); // everything ``` -------------------------------- ### Create a mutation with MutationOptions and execute Source: https://github.com/psachmann/dotnet-query/blob/main/README.md Creates a mutation using MutationOptions with a Mutator, InvalidateKeys, and OnSuccess callback. Execute the mutation with a request payload. ```csharp // Create a mutation var mutation = queryClient.CreateMutation(new MutationOptions { Mutator = (req, ct) => userService.CreateAsync(req, ct), InvalidateKeys = [QueryKey.From("users")], OnSuccess = (_, user) => Console.WriteLine($"Created {user.Name}"), }); mutation.Execute(new CreateUserRequest { Name = "Alice" }); ``` -------------------------------- ### Create QueryViewModel by injecting IQueryClient Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/mvvm.md Constructs a QueryViewModel directly by injecting IQueryClient. The view model creates and owns the query, so it must be disposed. ```csharp public sealed class UserPageViewModel : IDisposable { public UserPageViewModel(IQueryClient queryClient, IUserApi api) { User = new QueryViewModel( queryClient, new QueryOptions { KeyFactory = id => QueryKey.From("users", id), Fetcher = (id, ct) => api.GetUserAsync(id, ct), StaleTime = TimeSpan.FromMinutes(5), }); } public QueryViewModel User { get; } public void Load(int userId) => User.SetArgs(userId); public void Dispose() => User.Dispose(); } ``` -------------------------------- ### KeyFactory patterns for QueryKey.From Source: https://github.com/psachmann/dotnet-query/blob/main/docs/doc/guides/queries.md Shows three KeyFactory patterns: simple, parameterized, and compound. Keys should be descriptive and hierarchical to support invalidation with predicates like key.ToString().StartsWith("users"). ```csharp // Simple key — always the same data regardless of args KeyFactory = _ => QueryKey.From("config") // Parameterized key — different data per id KeyFactory = id => QueryKey.From("users", id) // Compound key — different data per user and type KeyFactory = (userId, type) => QueryKey.From("users", userId, "items", type) ``` -------------------------------- ### ToViewModel (IQuery) Source: https://github.com/psachmann/dotnet-query/blob/main/docs/llms.txt Wraps an IQuery instance into a QueryViewModel. The wrapper does not take ownership of the query; disposing the view model only releases the subscription. ```APIDOC ## ToViewModel (IQuery) ### Description Wraps an IQuery instance into a QueryViewModel. The wrapper does not take ownership of the query; disposing the view model only releases the subscription. ### Method Extension method ### Signature ```csharp public static QueryViewModel ToViewModel( this IQuery query, IUiDispatcher? dispatcher = null) where TData : class; ``` ### Parameters - **query** (IQuery) - Required - The query to wrap. - **dispatcher** (IUiDispatcher?) - Optional - The UI dispatcher to use for marshaling to the UI thread. ### Remarks This method is sugar for the wrapping constructor. It always wraps and never takes ownership. Dispose() releases only the subscription. ```