Official .NET SDK for Spice.ai - Query data with Apache Arrow Flight and parameterized queries using ADBC.
$ dotnet add package SpiceAIDotnet SDK for Spice.ai.
dotnet add package spiceai
Follow the quickstart guide to install and run Spice locally.
using Spice;
using var client = new SpiceClientBuilder().Build();
using Spice;
using var client = new SpiceClientBuilder().Build();
var data = await client.Query("SELECT * FROM my_table LIMIT 10;");
Use parameterized queries to prevent SQL injection and improve performance:
using Spice;
using var client = new SpiceClientBuilder().Build();
var parameters = new Dictionary<string, object>
{
{ "product_id", 42 },
{ "min_price", 10.0 }
};
var data = await client.Query(
"SELECT * FROM products WHERE id = :product_id AND price >= :min_price",
parameters);
For more control over parameter types, use QueryWithParams with positional placeholders ($1, $2, etc.). This uses the ADBC protocol and supports explicit type specification:
Note: The
QueryWithParamsmethod requires the ADBC FlightSQL driver to support prepared statements. The pure C# driver (Apache.Arrow.Adbc.Drivers.FlightSql) currently does not implementPrepare(). For full parameterized query support, the Go-based interop driver (Apache.Arrow.Adbc.Drivers.Interop.FlightSql) is required. See the Apache ADBC documentation for more details.
using Spice;
using Spice.Params;
using var client = new SpiceClientBuilder().Build();
// Basic usage with type inference
var data = await client.QueryWithParams(
"SELECT * FROM products WHERE id = $1 AND price >= $2",
42, // Inferred as Int32
10.50 // Inferred as Double
);
// Read the results as Arrow data
while (await data!.ReadNextRecordBatchAsync() is { } batch)
{
// Process batch...
}
Use the Param class for explicit control over Arrow data types:
using Spice;
using Spice.Params;
using var client = new SpiceClientBuilder().Build();
// Explicitly typed parameters
var data = await client.QueryWithParams(
"SELECT * FROM orders WHERE customer_id = $1 AND order_date >= $2 AND total > $3",
Param.Int64(12345), // Explicit Int64
Param.Date32(new DateTime(2024, 1, 1)), // Date without time
Param.Decimal128(100.00m, 10, 2) // Decimal with precision/scale
);
// Supported Param types:
// - Integers: Param.Int8, Int16, Int32, Int64, UInt8, UInt16, UInt32, UInt64
// - Floating point: Param.Float, Double
// - Text/Binary: Param.String, Binary
// - Boolean: Param.Boolean
// - Date/Time: Param.Date32, Date64, Time32, Time64, Timestamp
// - Duration: Param.DurationSeconds, DurationMilliseconds, DurationMicroseconds, DurationNanoseconds
// - Decimal: Param.Decimal128, Decimal256
// - Null: Param.Null
You can mix inferred and explicit types in the same query:
var data = await client.QueryWithParams(
"SELECT * FROM users WHERE name = $1 AND age > $2 AND verified = $3",
"John", // Inferred as String
Param.Int16(18), // Explicit Int16
true // Inferred as Boolean
);
Trigger a refresh of an accelerated dataset:
using Spice;
using var client = new SpiceClientBuilder().Build();
await client.RefreshDatasetAsync("my_dataset");
using Spice;
// Connect to Spice on a custom host/port
using var client = new SpiceClientBuilder()
.WithFlightAddress("grpc://my-server:50051")
.WithHttpAddress("http://my-server:8090")
.Build();
// Enable TLS for self-hosted Spice
using var tlsClient = new SpiceClientBuilder()
.WithFlightAddress("grpc+tls://my-server:50051")
.WithHttpAddress("https://my-server:8090")
.WithTls(true)
.Build();
using Spice;
using var client = new SpiceClientBuilder()
.WithSpiceCloud("API_KEY") // Automatically configures endpoints and enables TLS
.Build();
using Spice;
using var client = new SpiceClientBuilder()
.WithSpiceCloud("API_KEY")
.Build();
var data = await client.Query("SELECT * FROM eth.recent_blocks LIMIT 10;");
using Spice;
using var client = new SpiceClientBuilder()
.WithSpiceCloud("API_KEY")
.Build();
var parameters = new Dictionary<string, object>
{
{ "nation_name", "CHINA" },
{ "min_key", 0 }
};
var data = await client.Query(
"SELECT * FROM tpch.nation WHERE n_name = :nation_name AND n_nationkey >= :min_key",
parameters);
using Spice;
using var client = new SpiceClientBuilder()
.WithSpiceCloud("API_KEY")
.Build();
await client.RefreshDatasetAsync("my_dataset");
The SpiceClient implements IDisposable and should be properly disposed to release network resources (gRPC channels, HTTP clients). Use the using statement or using declaration for automatic disposal:
// Using statement (automatically disposes when scope exits)
using (var client = new SpiceClientBuilder().WithSpiceCloud("API_KEY").Build())
{
var data = await client.Query("SELECT * FROM tpch.customer LIMIT 10;");
// Process data...
} // Client is disposed here
// Or using declaration (C# 8.0+)
using var client = new SpiceClientBuilder().WithSpiceCloud("API_KEY").Build();
var data = await client.Query("SELECT * FROM tpch.customer LIMIT 10;");
// Client is disposed at end of scope
Important: Always dispose of SpiceClient instances to prevent resource leaks, especially in long-running applications or when creating multiple client instances.
Check out our Documentation to learn more about how to use the Dotnet SDK.