MyDevTime.SqlDataProvider
1.7.0
See the version list below for details.
dotnet add package MyDevTime.SqlDataProvider --version 1.7.0
NuGet\Install-Package MyDevTime.SqlDataProvider -Version 1.7.0
<PackageReference Include="MyDevTime.SqlDataProvider" Version="1.7.0" />
<PackageVersion Include="MyDevTime.SqlDataProvider" Version="1.7.0" />
<PackageReference Include="MyDevTime.SqlDataProvider" />
paket add MyDevTime.SqlDataProvider --version 1.7.0
#r "nuget: MyDevTime.SqlDataProvider, 1.7.0"
#:package MyDevTime.SqlDataProvider@1.7.0
#addin nuget:?package=MyDevTime.SqlDataProvider&version=1.7.0
#tool nuget:?package=MyDevTime.SqlDataProvider&version=1.7.0
MyDevTime.SqlDataProvider
A config-driven SQL data provider framework that exposes arbitrary database queries through multiple ASP.NET Core protocols. Define SQL queries in appsettings.json and the framework automatically exposes them via GraphQL, OData, SignalR, and gRPC — with schema auto-discovery, typed endpoints, filtering, sorting, and projection.
Supports .NET 8, .NET 9, and .NET 10.
Packages
Features
- Config-driven — define endpoints in
appsettings.json, no code changes needed - Schema auto-discovery — column names, types, and nullability discovered at startup via
SchemaOnlyexecution - Multiple protocols — GraphQL (HotChocolate), OData, SignalR, gRPC
- Multi-database — pluggable
IDbConnectionFactorywith built-in SQL Server support; add any ADO.NET provider (SQLite, PostgreSQL, MySQL, etc.) - SQL dialect abstraction —
ISqlDialecthandles paging syntax and column quoting per database (ANSI, SQL Server, PostgreSQL, MySQL built-in) - SQL injection prevention —
ValidColumnswhitelist enforced on all filter/sort/projection inputs - Filtering — per-endpoint opt-in with column-level control
- Sorting — single and multi-column, ASC/DESC
- Projection — auto-projection from GraphQL selection set; explicit column selection for other protocols
- Paging — offset-based with
skip/takeandtotalCountacross all protocols (default: 50 rows) - Hot-reload — OData, SignalR, and gRPC reload endpoint config on
appsettings.jsonchanges without restart
Quick Start
1. Install the packages
dotnet add package MyDevTime.SqlDataProvider
dotnet add package MyDevTime.SqlDataProvider.AspDotNetCore.GraphQl # and/or other protocols
2. Register the provider
// GraphQL
builder.Services
.AddGraphQLServer()
.AddQueryType(d => d.Name("Query").Field("_empty").Resolve(""))
.AddGraphQlSqlDataProvider();
// OData
builder.Services.AddODataSqlDataProvider();
// SignalR
builder.Services.AddSignalRSqlDataProvider();
// gRPC
builder.Services.AddGrpcSqlDataProvider();
3. Register a database connection factory
using MyDevTime.SqlDataProvider.Connection;
// SQL Server (built-in)
builder.Services.AddDbConnectionFactory("mssql", new SqlClientConnectionFactory());
// Custom providers
builder.Services.AddDbConnectionFactory("sqlite", new SqliteConnectionFactory());
builder.Services.AddDbConnectionFactory("postgres", new NpgsqlConnectionFactory());
ProviderName in the profile config selects which factory to use. Duplicate registrations throw at startup.
4. Configure endpoints in appsettings.json
{
"SqlRetrievalProfiles": [
{
"ProfileName": "AdventureWorks",
"ConnectionString": "Server=...;Database=AdventureWorks;...",
"ProviderName": "mssql",
"RetrievalEndpointProfiles": [
{
"EndPointName": "Customers",
"SqlQuery": "SELECT CustomerID, FirstName, LastName, EmailAddress FROM Customer",
"AllowFiltering": true,
"AllowSorting": true,
"AllowProjection": true
},
{
"EndPointName": "Products",
"SqlQuery": "SELECT ProductID, Name, ListPrice FROM Product WHERE ListPrice > 0",
"AllowFiltering": true,
"AllowSorting": true,
"AllowProjection": false
}
]
}
]
}
Column lists for filtering/sorting/projection are auto-discovered from the query at startup. You can also pin them explicitly:
{
"EndPointName": "Customers",
"SqlQuery": "SELECT CustomerID, FirstName, LastName FROM Customer",
"AllowFiltering": true,
"FilterableColumns": ["LastName"],
"AllowSorting": true,
"AllowProjection": true
}
Explicit lists are never overwritten by discovery.
Protocol Usage
GraphQL
Endpoints are exposed as top-level query fields named {ProfileName}_{EndPointName}. Results are wrapped in a paged response with items and totalCount.
# Basic query — auto-projects from selection set
{
AdventureWorks_Customers {
items { customerID firstName lastName }
totalCount
}
}
# Filtering (HotChocolate-style where input)
{
AdventureWorks_Customers(where: { lastName: { eq: "Miller" } }) {
items { customerID firstName lastName }
totalCount
}
}
# Multiple conditions (AND)
{
AdventureWorks_Customers(
where: { lastName: { eq: "Miller" }, firstName: { startsWith: "J" } }
) {
items { customerID firstName lastName }
}
}
# OR conditions
{
AdventureWorks_Customers(
where: { or: [{ lastName: { eq: "Miller" } }, { lastName: { eq: "Smith" } }] }
) {
items { customerID firstName lastName }
}
}
# HC-style sorting
{
AdventureWorks_Customers(order: [{ lastName: ASC }, { firstName: DESC }]) {
items { lastName firstName }
}
}
# Paging
{
AdventureWorks_Customers(skip: 20, take: 10) {
items { customerID firstName lastName }
totalCount
}
}
Filter field names are camelCased. Supported operators by type:
| Type | Operators |
|---|---|
| String | eq neq contains ncontains startsWith nstartsWith endsWith nendsWith in nin |
| Int / Long | eq neq gt gte lt lte in nin |
| Float / Decimal | eq neq gt gte lt lte in nin |
| Boolean | eq neq |
| DateTime / Date | eq neq gt gte lt lte in nin |
| UUID | eq neq in nin |
Note: GraphQL schema is built at startup. Adding or removing endpoints requires a restart.
OData
GET /odata/{ProfileName}/{EndPointName}
GET /odata/AdventureWorks/Customers?$filter=LastName eq 'Miller'
GET /odata/AdventureWorks/Customers?$orderby=LastName asc
GET /odata/AdventureWorks/Customers?$select=CustomerID,FirstName,LastName
GET /odata/AdventureWorks/Customers?$top=10&$skip=20
GET /odata/AdventureWorks/Customers?$count=true
| Query Parameter | Description |
|---|---|
$filter |
Filter expression (see below) |
$orderby |
Sort expression (e.g. Name asc, Price desc) |
$select |
Comma-separated column names to return |
$top |
Max rows to return (default: 50 if omitted) |
$skip |
Number of rows to skip |
$count |
Set to true to include total count. Changes response from [...] to { value: [...], count: N } |
Supported $filter operators:
| OData Operator | SQL Equivalent | Example |
|---|---|---|
eq |
= |
$filter=LastName eq 'Miller' |
ne |
!= |
$filter=Color ne 'Red' |
gt |
> |
$filter=ListPrice gt 100 |
ge |
>= |
$filter=ListPrice ge 50 |
lt |
< |
$filter=ListPrice lt 200 |
le |
<= |
$filter=ListPrice le 1000 |
Logical combinators and / or and parenthesized grouping are supported (and binds tighter than or):
$filter=Color eq 'Red' and ListPrice gt 100
$filter=Color eq 'Red' or Color eq 'Blue'
$filter=(LastName eq 'Miller' or LastName eq 'Smith') and FirstName eq 'John'
SignalR
Hub URL: /sqlDataHub (configurable via MapSqlDataProviderHub("/custom-path"))
Connection
const connection = new signalR.HubConnectionBuilder()
.withUrl("/sqlDataHub")
.build();
await connection.start();
Hub Method: ExecuteQuery
ExecuteQuery(profileName, endpointName, filters?, sorting?, projection?, skip?, take?)
-> { data, totalCount, success } | { error, success }
| Parameter | Type | Description |
|---|---|---|
profileName |
string |
Profile name from config |
endpointName |
string |
Endpoint name within the profile |
filters |
array (nullable) |
Array of { column, operator, value } filter descriptors |
sorting |
array (nullable) |
Array of { column: string, ascending: bool } objects |
projection |
array (nullable) |
Array of column name strings to return |
skip |
int (nullable) |
Number of rows to skip (for paging) |
take |
int (nullable) |
Max rows to return (default: 50 if omitted) |
Supported filter operators: =, !=, <>, >, >=, <, <=, LIKE, NOT LIKE, IN, NOT IN
Multiple filters are combined with AND. Values are auto-unwrapped from JSON.
Examples
// Simple query (returns first 50 rows by default)
const result = await connection.invoke(
"ExecuteQuery", "AdventureWorks", "Customers",
null, null, null, null, null
);
// With filter
const filtered = await connection.invoke(
"ExecuteQuery", "AdventureWorks", "Customers",
[{ column: "LastName", operator: "=", value: "Miller" }],
null, null, null, null
);
// With sorting and paging
const page2 = await connection.invoke(
"ExecuteQuery", "AdventureWorks", "Customers",
null,
[{ column: "LastName", ascending: true }],
null,
20, // skip
20 // take
);
// page2.totalCount = 847, page2.data contains rows 21-40
gRPC
Service Definition
service SqlDataProviderGrpc {
rpc ExecuteQuery (QueryRequest) returns (QueryResponse);
rpc GetCatalog (CatalogRequest) returns (CatalogResponse);
}
GetCatalog — Discover Available Endpoints
Returns all profiles, their endpoints, discovered column schemas, and allowed column lists.
var catalog = await client.GetCatalogAsync(new CatalogRequest());
foreach (var profile in catalog.Profiles)
{
Console.WriteLine($"Profile: {profile.Name}");
foreach (var endpoint in profile.Endpoints)
{
Console.WriteLine($" Endpoint: {endpoint.Name}");
foreach (var col in endpoint.Columns)
Console.WriteLine($" Column: {col.Name} ({col.ClrType}, nullable={col.Nullable})");
}
}
ExecuteQuery — Run a Query
| Field | Type | Description |
|---|---|---|
profile_name |
string |
Profile name from config |
endpoint_name |
string |
Endpoint name within the profile |
filters |
repeated FilterDescriptor |
Zero or more filter conditions |
sorting |
repeated SortDescriptor |
Zero or more sort terms |
projection |
repeated string |
Column names to return (empty = all columns) |
skip |
optional int32 |
Number of rows to skip |
take |
optional int32 |
Max rows to return (default: 50) |
Response contains success, error, json_data (JSON-serialized row array), and total_count.
Supported filter operators: =, !=, <>, >, >=, <, <=, LIKE, NOT LIKE, IN, NOT IN
Examples
// Simple query
var response = await client.ExecuteQueryAsync(new QueryRequest {
ProfileName = "AdventureWorks",
EndpointName = "Customers"
});
// With filter
var filtered = await client.ExecuteQueryAsync(new QueryRequest {
ProfileName = "AdventureWorks",
EndpointName = "Customers",
Filters = { new FilterDescriptor { Column = "LastName", Operator = "=", Value = "Miller" } }
});
// With sorting and paging
var page2 = await client.ExecuteQueryAsync(new QueryRequest {
ProfileName = "AdventureWorks",
EndpointName = "Customers",
Sorting = { new SortDescriptor { Column = "CustomerID", Ascending = true } },
Skip = 20,
Take = 20
});
Multi-Database Setup
Each profile specifies a ProviderName that maps to a registered IDbConnectionFactory. The factory's Dialect property controls SQL generation (paging syntax, column quoting):
{
"SqlRetrievalProfiles": [
{
"ProfileName": "Sales",
"ConnectionString": "Server=sales-server;...",
"ProviderName": "mssql",
"RetrievalEndpointProfiles": [...]
},
{
"ProfileName": "Archive",
"ConnectionString": "Host=archive-server;Database=archive;...",
"ProviderName": "postgres",
"RetrievalEndpointProfiles": [...]
}
]
}
builder.Services.AddDbConnectionFactory("mssql", new SqlClientConnectionFactory()); // SqlServerDialect
builder.Services.AddDbConnectionFactory("postgres", new NpgsqlConnectionFactory()); // custom dialect
Custom Connection Factory
Implement IDbConnectionFactory for any ADO.NET provider:
using MyDevTime.SqlDataProvider.Connection;
using MyDevTime.SqlDataProvider.Dialect;
public class NpgsqlConnectionFactory : IDbConnectionFactory
{
public ISqlDialect Dialect => PostgreSqlDialect.Instance;
public DbConnection CreateConnection(string connectionString)
=> new NpgsqlConnection(connectionString);
}
Built-in dialects: AnsiSqlDialect (default), SqlServerDialect, PostgreSqlDialect, MySqlDialect.
Build & Test
dotnet build # Build entire solution
dotnet test # Run all 213 tests across 5 projects
dotnet pack # Package all libraries as NuGet
Target frameworks: net8.0, net9.0, net10.0. Solution uses .slnx format.
License
Apache 2.0 — see LICENSE.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 is compatible. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Dapper (>= 2.1.72)
- Microsoft.Data.SqlClient (>= 6.1.4)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.5)
- Microsoft.Extensions.Logging (>= 10.0.5)
-
net8.0
- Dapper (>= 2.1.72)
- Microsoft.Data.SqlClient (>= 6.1.4)
- Microsoft.Extensions.Configuration.Binder (>= 8.0.2)
- Microsoft.Extensions.Logging (>= 8.0.1)
-
net9.0
- Dapper (>= 2.1.72)
- Microsoft.Data.SqlClient (>= 6.1.4)
- Microsoft.Extensions.Configuration.Binder (>= 9.0.7)
- Microsoft.Extensions.Logging (>= 9.0.9)
NuGet packages (5)
Showing the top 5 NuGet packages that depend on MyDevTime.SqlDataProvider:
| Package | Downloads |
|---|---|
|
MyDevTime.SqlDataProvider.AspDotNetCore.SignalR
SignalR protocol support for MyDevTime SQL data provider. Exposes SQL queries as SignalR hub methods with filtering, sorting, projection, and paging. |
|
|
MyDevTime.SqlDataProvider.AspDotNetCore.gRPC
gRPC protocol support for MyDevTime SQL data provider. Exposes SQL queries as gRPC services with protobuf-defined request/response, filtering, sorting, projection, and paging. |
|
|
MyDevTime.SqlDataProvider.AspDotNetCore.GraphQl
GraphQL protocol support for MyDevTime SQL data provider. Powered by HotChocolate with auto-generated strongly-typed schemas, filtering, sorting, projection, and offset paging. |
|
|
MyDevTime.SqlDataProvider.AspDotNetCore.OData
OData protocol support for MyDevTime SQL data provider. Exposes SQL queries as OData endpoints with $filter, $orderby, $select, $top, $skip, and $count. |
|
|
MyDevTime.SqlDataProvider.AspDotNetCore.Mcp
Model Context Protocol (MCP) support for MyDevTime SQL data provider. Exposes SQL query endpoints as MCP tools for AI assistants with filtering, sorting, projection, and paging. |
GitHub repositories
This package is not used by any popular GitHub repositories.