One unified API for cross-vendor .NET database access
Write your data layer once. Deploy it against DB2, MySQL, Oracle, PostgreSQL, SQL Server, SQLite or Teradata with zero code changes.
using YndigoBlue.Velocity.Engine;
using YndigoBlue.Velocity.Model;
using YndigoBlue.Velocity.Attributes;
public class Order
{
[VelocityField("order_id")] public int OrderId { get; set; }
[VelocityField("customer")] public string Customer { get; set; }
[VelocityField("total")] public decimal? Total { get; set; }
[VelocityField("created_at")] public DateTime? Created { get; set; }
// Datastore is your application's own static holder for the loaded Schema
public static Table Table { get; } = Datastore.Schema["order"];
}
// Type-safe query, no raw SQL
var query = new Query()
.Select([Order.Table["order_id"],
Order.Table["customer"],
Order.Table["total"]])
.From(Order.Table)
.Where(new Criterion<decimal>(Order.Table["total"], ConditionalType.GreaterThan, 100m))
.OrderBy(Order.Table["created_at"], OrderClauseType.Descending);
// Works identically on every supported database
using var manager = new Manager(connection);
using var results = manager.Retrieve(query);
IList<Order> orders = results.BindResults<Order>(BindingType.Attribute);
Everything your data layer needs
Velocity handles the complexity of cross-database access so you can focus on your application, not the plumbing.
Cross-Vendor Compatibility
Connect to DB2, MySQL, Oracle, PostgreSQL, SQL Server, SQLite and Teradata through one consistent API. Switch vendors by changing a single configuration line. Writing a Cross-Database .NET Application works through what that takes in practice.
Single Codebase
Write your data access layer once with no vendor-specific SQL dialects or separate repositories. One clean, maintainable codebase that runs everywhere.
Type-Safe Query Builder
Compose queries with a fluent, type-safe API, with no raw SQL strings anywhere in your application. Criteria, joins, aggregates and ordering all handled at the .NET layer, with results mapped straight onto your own classes. Data Binding for Relational Tables covers the mapping in full.
Declarative Schema Management
Define your database schema in XML, JSON or YAML and load it with a few lines of code. Version, migrate and refresh schemas across environments with confidence.
Batch Data Import
Import large datasets efficiently with Velocity's unified batch API. Handles bulk inserts and transactions consistently across all supported vendors.
Geospatial Support
First-class support for Geography and Geometry types. Store and query spatial data using familiar .NET types, with no vendor-specific spatial SQL required. See Spatial Data Across Relational Databases for how the same query behaves on each engine, and Importing and Exporting GeoJSON for moving features in and out.
Runs on every major database
Whether your organization standardizes on an enterprise warehouse or a lightweight embedded database, Velocity has you covered.
Clean. Expressive. Familiar.
Velocity's API feels natural to any .NET developer. Attribute-based model mapping, a composable query builder and strongly-typed results, with no raw SQL anywhere.
using YndigoBlue.Velocity.Attributes;
using YndigoBlue.Velocity.Model;
/// <summary>
/// Decorate properties with [VelocityField] to map columns to .NET properties automatically.
/// </summary>
public class Product
{
[VelocityField("product_id")]
public int ProductId { get; set; }
[VelocityField("product_name")]
public string Name { get; set; }
[VelocityField("unit_price")]
public decimal Price { get; set; }
[VelocityField("category")]
public string Category { get; set; }
[VelocityField("created_at")]
public DateTime CreatedAt { get; set; }
// Resolved once from the schema, then reused everywhere. Datastore is your
// application's own static holder for the Schema it loaded at startup.
public static Table Table { get; } = Datastore.Schema["product"];
}
using YndigoBlue.Velocity.Engine;
using YndigoBlue.Velocity.Model;
// Manager is the entry point and owns the connection for its lifetime
using var manager = new Manager(connection);
// Compose a type-safe query with no raw SQL strings in application code
// Multiple criteria are combined in an array with BooleanItem connectors
var query = new Query()
.Select([Product.Table["product_id"],
Product.Table["product_name"],
Product.Table["unit_price"],
Product.Table["category"]])
.From(Product.Table)
.Where([
new Criterion<decimal>(Product.Table["unit_price"], ConditionalType.GreaterThan, 50.00m),
new BooleanItem(BooleanType.And),
new Criterion<string>(Product.Table["category"], "Electronics")
])
.OrderBy(Product.Table["unit_price"], OrderClauseType.Descending);
// BindResults maps each result row to a typed object via [VelocityField]
IList<Product> products = manager
.Retrieve(query)
.BindResults<Product>(BindingType.Attribute);
// Update a record: pass the criterion to the constructor and use the typed Set methods
var update = new Update(Product.Table,
new Criterion<int>(Product.Table["product_id"], 42));
update.SetFieldDecimal("unit_price", 45.00m);
manager.UpdateRecords(update);
using YndigoBlue.Velocity.Engine;
using YndigoBlue.Velocity.Model;
var orderTable = Datastore.Schema["order"];
var customerTable = Datastore.Schema["customer"];
// Aggregates: count, sum, avg, min, max
var revenueExpr = new Expression("total_revenue", new Aggregate(AggregateType.Sum, orderTable["amount"]));
var countExpr = new Expression("order_count", new Aggregate(AggregateType.Count, orderTable["order_id"]));
// Join on customer_id and group by customer. The constructor defaults to an inner
// equality join; other overloads take the join type and operator explicitly.
var query = new Query()
.Select([customerTable["name"], revenueExpr, countExpr])
.From(new Join(orderTable, customerTable, "customer_id"))
.GroupBy(customerTable["name"])
.OrderBy(revenueExpr);
// Bind to a dedicated result model
IList<CustomerRevenue> topCustomers = manager
.Retrieve(query)
.BindResults<CustomerRevenue>(BindingType.Attribute);
using YndigoBlue.Velocity.Engine;
using YndigoBlue.Velocity.Enums;
using YndigoBlue.Velocity.Model;
// Load schema from a versioned XML file and cache it globally
Datastore.Schema = manager.ReadSchemaFromFile("Config/myapp-v3.xml", ConfigType.Xml);
// Verify the live database matches, and fall back to the previous version if not
Schema liveSchema = manager.LoadSchema("myapp");
if (!liveSchema.Equals(Datastore.Schema))
{
Datastore.Schema = manager.ReadSchemaFromFile("Config/myapp-v2.xml", ConfigType.Xml);
}
// Deploy a schema update (e.g. new columns, new tables) in a transaction
string newSchemaXml = File.ReadAllText("Config/myapp-v4.xml");
Datastore.Schema = manager.ReadSchema(newSchemaXml, ConfigType.Xml);
manager.BeginTransaction();
manager.UpdateSchema(Datastore.Schema);
manager.CommitTransaction();
// Rebuild the schema from scratch, dropping and recreating every table
manager.BuildSchema(Datastore.Schema, overwrite: true);
using YndigoBlue.Velocity.Engine;
using YndigoBlue.Velocity.Model;
// ── CSV import ──
// ImportConfig takes the target table, date format, and whether to retain identity values from file
var config = new ImportConfig(Product.Table, DateFormatType.Iso, keepIdentity: true);
manager.ImportData(Product.Table, "data/products.csv", config);
// ── Export a schema's definition and every table's data to a folder ──
manager.ExportSchema(
Datastore.Schema,
ConfigType.Xml,
"data/export/",
new ExportConfig(DateFormatType.Iso));
// ── Insert a single record using typed field setters ──
var record = new Record(Product.Table);
record.SetFieldInteger("product_id", 1001);
record.SetFieldVarChar("product_name", "Widget Pro");
record.SetFieldDecimal("unit_price", 49.99m);
record.SetFieldVarChar("category", "Electronics");
record.SetFieldDateTime("created_at", DateTime.UtcNow);
manager.AddRecord(record);
using YndigoBlue.Velocity.Engine;
using YndigoBlue.Velocity.Enums;
using YndigoBlue.Velocity.Functions;
using YndigoBlue.Velocity.Model;
// ── Store a geography value ──
Geography london = GeospatialUtils.GeographyFromText("POINT (-0.1276 51.5074)");
var record = new Record(Location.Table);
record.SetFieldVarChar("name", "London");
record.SetFieldGeography("coordinates", london);
manager.AddRecord(record);
// ── Proximity query: find all locations within 50 km of a reference point ──
Geography origin = GeospatialUtils.GeographyFromText("POINT (-1.8904 52.4862)");
// SpatialDistance computes ST_Distance between the column and the reference point
var distanceFn = new SpatialDistance(
Location.Table["coordinates"],
origin,
UnitOfMeasure.Kilometer);
// Wrap in an Expression so it can appear in both SELECT and WHERE
var distanceExpr = new Expression("distance_km", distanceFn);
var query = new Query()
.Select([Location.Table["name"], Location.Table["coordinates"], distanceExpr])
.From(Location.Table)
.Where(new Criterion<double>(distanceExpr, ConditionalType.LessThan, 50.0))
.OrderBy(distanceExpr, OrderClauseType.Ascending);
IList<Location> nearby = manager
.Retrieve(query)
.BindResults<Location>(BindingType.Attribute);
One API between your code and every database
Your application describes what it wants with Velocity's types. Velocity runs it directly on the database and hands the results back as your own objects.
Model classes
Your own classes, with [VelocityField] attributes mapping properties to columns.
Queries and records
Built with Query, Record and Update instead of SQL strings.
Schema definitions
XML, JSON or YAML files that describe your tables, views and indexes.
objects out
- Works the same on every database
- Maps results onto your classes
- Builds and updates schemas
- Imports and exports data
- Stores and queries spatial data
the same engine
Velocity from the command line, for schema builds, data loads and deployments in your CI/CD pipeline.
on each engine
- DB2
- MySQL
- Oracle
- PostgreSQL
- SQL Server
- SQLite
- Teradata
Ready to get started?
The full Velocity Framework documentation covers installation, connection configuration, schema management, query building, joins, aggregates, geospatial data, and batch import.