LinKit.Data 1.1.0

There is a newer version of this package available.
See the version list below for details.
dotnet add package LinKit.Data --version 1.1.0
                    
NuGet\Install-Package LinKit.Data -Version 1.1.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="LinKit.Data" Version="1.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="LinKit.Data" Version="1.1.0" />
                    
Directory.Packages.props
<PackageReference Include="LinKit.Data" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add LinKit.Data --version 1.1.0
                    
#r "nuget: LinKit.Data, 1.1.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package LinKit.Data@1.1.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=LinKit.Data&version=1.1.0
                    
Install as a Cake Addin
#tool nuget:?package=LinKit.Data&version=1.1.0
                    
Install as a Cake Tool

LinKit.Data

NuGet Version NuGet Downloads

LinKit.Data is a source-generated ADO.NET data access and schema synchronization library. Generated repository and reader-mapping paths are designed to reduce runtime discovery. LINQ closure extraction and other specific paths may still use runtime reflection/dynamic code; the repository AOT smoke app covers only a selected feature set.


💡 The Philosophy: Developer Does Only 2 Things

  1. Declare Domain Model / Entity: Add, remove, or modify properties and configuration attributes directly on C# classes.
  2. Execute Business Logic in CQRS Handlers: Inject source-generated IRepository<TEntity, TId> and perform CRUD, raw SQL execution, or fluent queries with blazing-fast compiled ADO.NET speed.

LinKit does not require EF Core. It provides additive schema synchronization and versioned migrations for changes that need explicit data/schema operations.


🚀 Key Features

  • Additive schema synchronization: At application startup, LinKit can create missing tables and add supported missing columns/indexes for SQLite, PostgreSQL, SQL Server, MySQL, and Oracle. It is not a general migration engine and does not safely infer renames, drops, or arbitrary existing-column changes.
  • EF Core Equivalent Attributes: Configure lengths, precisions, scales, foreign keys, check constraints, comments, unicode, json columns, and optimistic concurrency.
  • Compile-Time Code Generation: Repository implementations, DataReader mapping, and standard SQL operations are generated at compile time. Custom LINQ closure values are currently extracted from expression trees using runtime member access; this path is exercised under Native AOT but is not reflection-free.
  • Native AOT JSON Serialization ([JsonColumn]):
    • Compile-time Specific Context: [JsonColumn(typeof(AppJsonContext))] can bind to AppJsonContext.Default for the configured type.
    • Ambient Global Context: options.UseJsonSerializerContext(AppJsonContext.Default) across repositories.
  • Explicit query parameter collections:
    • Generated QuerySqlAsync / ExecuteSqlAsync paths support IEnumerable<KeyValuePair<string, object?>>, IEnumerable<(string Name, object? Value)>, and IEnumerable<DbParameter> without reflecting over anonymous parameter objects.
    • Anonymous parameter objects are not supported by generated raw-SQL repositories. Use one of the explicit parameter collection shapes above.
  • Full CLR & Database Type Coverage: Built-in code generation for int, long, short, byte, sbyte, uint, ulong, ushort, bool, char, decimal, double, float, Guid, DateTime, DateTimeOffset, DateOnly, TimeOnly, TimeSpan, byte[], Enum, JsonColumn, and their Nullable equivalents.
  • Comprehensive Repository Abstractions:
    • Standard CRUD: GetByIdAsync, ListAsync, InsertAsync, UpdateAsync, DeleteAsync
    • Bulk Operations: InsertRangeAsync, UpdateRangeAsync, DeleteRangeAsync
    • Pagination: GetPagedAsync(pageNumber, pageSize) returning PagedResult<T>
    • Arbitrary SQL Execution: QuerySqlAsync, QuerySingleSqlAsync, ExecuteSqlAsync, ExecuteScalarSqlAsync
    • Fluent Query Builder: repo.Query().Where(...).OrderBy(...).Skip(...).Take(...).ToPagedListAsync(...)
  • Unit of Work & Ambient Transactions: Repositories using the same scoped UoW can enlist commands in its current transaction.

Raw SQL and string-based fluent conditions are intentionally passed through to the database. Always bind untrusted values as parameters; never concatenate user input into SQL text. Prefer the generated LINQ query API where it supports the required expression.

The built-in soft-delete filter applies to generated repository and fluent/LINQ queries. Raw SQL and custom [SqlQuery] repository methods are application-controlled and must include the appropriate soft-delete predicate themselves. When combining multiple fluent .Where(...) calls, use unique parameter names across all calls (for example, @categoryId and @supplierId); the query builder does not rewrite SQL parameter names.


🏷️ Comprehensive Configuration Attributes

Attribute Scope Description
[Table("name", Schema = "...", Comment = "...")] Class Maps entity to database table with optional schema and documentation comment.
[PrimaryKey(AutoIncrement = true)] Property Designates primary key with optional identity/auto-increment.
[Column("col_name", CustomSqlType = "...")] Property Overrides column name or explicit raw SQL type definition.
[MaxLength(200)] Property Defines max length for VARCHAR(n) / NVARCHAR(n) / VARBINARY(n).
[FixedLength(10)] Property Configures fixed character columns: CHAR(n) / NCHAR(n).
[DecimalPrecision(18, 2)] Property Configures precision and scale: DECIMAL(18, 2) / NUMERIC(18, 2).
[DefaultValueSql("0")] Property Sets raw SQL default value expression during column creation.
[ForeignKey("other_table", TargetColumn = "Id", OnDelete = OnDeleteAction.Cascade)] Property Configures foreign key constraint with referential delete actions.
[CheckConstraint("Price >= 0", Name = "chk_price")] Class / Property Enforces SQL check constraint in database DDL.
[Comment("...")] Class / Property Attaches documentation comment in database metadata.
[Unicode(false)] Property Sets string storage to ASCII/ANSI VARCHAR vs NVARCHAR.
[JsonColumn] Property Automatically serializes complex objects/lists to/from JSON/JSONB.
[ConcurrencyCheck] Property Marks column for optimistic concurrency tracking.
[IgnoreColumn] Property Excludes property from database mapping.
[Indexed(IsUnique = true, IndexName = "...")] Property Automatically builds single-column index on startup.
[CompositeIndex("IX_Name_Age", nameof(Name), nameof(Age), IsUnique = true)] Class Defines a multi-column index.
[SoftDelete("DeletedAt")] Class Enables soft-delete behavior using the specified timestamp column. Map that column as a nullable entity property or create it in a migration; the generator warns if it is missing.
[MultiTenant("TenantId")] Class Records tenant-column metadata only. It does not enforce tenant isolation; the generator emits a warning. Add explicit tenant predicates to every query.
[DataRepository] Interface Directs source generator to implement custom repository interfaces.
[SqlQuery("SELECT ...")] Method Declarative custom query method generation.

📦 Installation

dotnet add package LinKit.Data

Add your database ADO.NET driver:

# For SQLite
dotnet add package Microsoft.Data.Sqlite

# For PostgreSQL
dotnet add package Npgsql

# For SQL Server
dotnet add package Microsoft.Data.SqlClient

# For MySQL
dotnet add package MySqlConnector

# For Oracle
dotnet add package Oracle.ManagedDataAccess.Core

🛠️ Usage Example

1. Declare Domain Entity

using LinKit.Data;

public enum ProductStatus { Draft = 0, Active = 1, Archived = 2 }

[Table("catalog_products", Comment = "Catalog inventory table")]
[CheckConstraint("Price >= 0", Name = "chk_product_price_positive")]
public class ProductEntity
{
    [PrimaryKey]
    public Guid Id { get; set; }

    [Indexed]
    [MaxLength(200)]
    public string Name { get; set; } = string.Empty;

    [DecimalPrecision(18, 2)]
    public decimal Price { get; set; }

    [DefaultValueSql("0")]
    public int Stock { get; set; } = 0;

    [MaxLength(50)]
    public string? Sku { get; set; }

    public bool IsFeatured { get; set; } = false;

    public ProductStatus Status { get; set; } = ProductStatus.Active;

    public DateOnly? ReleasedDate { get; set; }

    public TimeOnly? DailyRestockTime { get; set; }

    [JsonColumn]
    public List<string>? Tags { get; set; }

    public DateTimeOffset CreatedAt { get; set; } = DateTimeOffset.UtcNow;
}

2. Configure in Program.cs

using LinKit.Data;
using Microsoft.Data.Sqlite;

var builder = WebApplication.CreateBuilder(args);

// Register LinKit.Data with automatic schema synchronization
builder.Services.AddLinKitData(options =>
{
    // SQLite:
    options.UseConnection(DatabaseDialect.Sqlite, () => new SqliteConnection("Data Source=app.db"));
    
    // Or PostgreSQL:
    // options.UseConnection(DatabaseDialect.PostgreSql, () => new NpgsqlConnection(connStr));
    
    // Or SQL Server:
    // options.UseConnection(DatabaseDialect.SqlServer, () => new SqlConnection(connStr));

    // Or MySQL:
    // options.UseConnection(DatabaseDialect.MySql, () => new MySqlConnection(connStr));

    // Or Oracle:
    // options.UseConnection(DatabaseDialect.Oracle, () => new OracleConnection(connStr));

    options.AutoSyncSchema = true; // Creates missing schema objects and adds supported missing columns.
});

3. Inject Repository in CQRS Handlers

Option A: LINQ Query Comprehension (from ... in ... select)

using LinKit.Core.Cqrs;
using LinKit.Data;

[CqrsHandler]
public class GetFeaturedProductsHandler(IRepository<ProductEntity, Guid> repository) 
    : IQueryHandler<GetFeaturedProductsQuery, List<ProductEntity>>
{
    public async Task<List<ProductEntity>> HandleAsync(GetFeaturedProductsQuery query, CancellationToken ct)
    {
        // Supports only the LINQ expression subset implemented by LinKit.Data.
        var q = from p in repository.AsQueryable()
                where p.Price >= query.MinPrice && p.IsFeatured
                orderby p.Price descending
                select p;

        return await q.ToListAsync(ct);
    }
}

Option B: Fluent Query Builder

using LinKit.Core.Cqrs;
using LinKit.Data;

[CqrsHandler]
public class GetProductsPagedHandler(IRepository<ProductEntity, Guid> repository) 
    : IQueryHandler<GetProductsPagedQuery, PagedResult<ProductEntity>>
{
    public async Task<PagedResult<ProductEntity>> HandleAsync(GetProductsPagedQuery query, CancellationToken ct)
    {
        return await repository.Query()
            .Where("Price >= @minPrice", new[] { (Name: "minPrice", Value: (object?)query.MinPrice) })
            .OrderBy("Price")
            .ToPagedListAsync(query.Page, query.PageSize, ct);
    }
}

4. Versioned Data & Schema Migrations ([DataMigration])

For master data seeding, backfills, or intentional schema alterations (rename column, drop column):

using LinKit.Data;

[DataMigration("20261021_01", Description = "Seed initial catalog products and categories")]
public class SeedCatalogInitialProductsMigration : IDataMigration
{
    public async Task UpAsync(IMigrationContext context, CancellationToken ct = default)
    {
        // 1. Safe DDL operations across all 5 databases (if needed)
        // await context.RenameColumnAsync("catalog_products", "OldSku", "Sku", ct);
        // await context.DropColumnAsync("catalog_products", "ObsoleteColumn", ct);

        // 2. Check existence & Seed initial data
        if (!await context.ExistsAsync(
            "catalog_products",
            "\"Sku\" = @sku",
            new Dictionary<string, object?> { ["sku"] = "SEED-001" },
            ct))
        {
            var repo = context.GetRepository<ProductEntity, Guid>();
            await repo.InsertAsync(new ProductEntity
            {
                Id = Guid.NewGuid(),
                Name = "Master Seed Product",
                Price = 149.99m,
                Sku = "SEED-001",
                Status = ProductStatus.Active,
                CreatedAt = DateTimeOffset.UtcNow
            }, ct);
        }
    }
}
  • Compile-time Discovery: Migration registrations are generated by LinKit.Data.Generator; the complete migration and provider path still requires application-level AOT validation.
  • Idempotent Execution: History tracked in __linkit_migrations table and executed once in exact version order inside a transaction.

📄 License

Licensed under the MIT License.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on LinKit.Data:

Package Downloads
LinKit.Outbox.Data

Provider-neutral ADO.NET durable stores for LinKit Outbox and Inbox.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
2.3.1 113 10/31/2026
2.3.0 104 10/30/2026
2.2.0 97 10/29/2026
2.1.0 100 10/25/2026
2.0.0 92 10/24/2026
1.1.1 99 10/24/2026
1.1.0 106 10/22/2026