CloudflareD1.NET.Linq 1.2.1

There is a newer version of this package available.
See the version list below for details.
dotnet add package CloudflareD1.NET.Linq --version 1.2.1
                    
NuGet\Install-Package CloudflareD1.NET.Linq -Version 1.2.1
                    
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="CloudflareD1.NET.Linq" Version="1.2.1" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="CloudflareD1.NET.Linq" Version="1.2.1" />
                    
Directory.Packages.props
<PackageReference Include="CloudflareD1.NET.Linq" />
                    
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 CloudflareD1.NET.Linq --version 1.2.1
                    
#r "nuget: CloudflareD1.NET.Linq, 1.2.1"
                    
#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 CloudflareD1.NET.Linq@1.2.1
                    
#: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=CloudflareD1.NET.Linq&version=1.2.1
                    
Install as a Cake Addin
#tool nuget:?package=CloudflareD1.NET.Linq&version=1.2.1
                    
Install as a Cake Tool

CloudflareD1.NET.Linq

LINQ query builder and object mapping extensions for CloudflareD1.NET. Provides type-safe query construction, automatic entity mapping, and fluent API for building SQL queries.

Installation

dotnet add package CloudflareD1.NET.Linq

Note: This package automatically includes CloudflareD1.NET as a dependency.

Features

  • Fluent query builder - Chain methods like .Where(), .OrderBy(), .Take(), .Skip()
  • Generic query methods - QueryAsync<T>(), QueryFirstOrDefaultAsync<T>(), etc.
  • Automatic entity mapping - Maps query results to strongly-typed objects
  • Snake_case to PascalCase conversion - Automatically maps database columns to C# properties
  • Parameterized queries - Safe from SQL injection with ? placeholders
  • Pagination support - Easy Take() and Skip() for paging
  • Aggregate functions - CountAsync(), AnyAsync()
  • Nullable type support - Handles nullable properties correctly
  • Custom mappers - Implement IEntityMapper for custom mapping logic
  • Performance optimized - Uses reflection caching for fast mapping

Quick Start

1. Define Your Entities

public class User
{
    public int Id { get; set; }
    public string Name { get; set; } = string.Empty;
    public string? Email { get; set; }
    public DateTime CreatedAt { get; set; }
    public bool IsActive { get; set; }
}

2. Use the Fluent Query Builder

using CloudflareD1.NET.Linq;

// Simple query with filtering
var activeUsers = await client.Query<User>("users")
    .Where("is_active = ?", true)
    .OrderBy("name")
    .ToListAsync();

// Pagination
var page2Users = await client.Query<User>("users")
    .OrderBy("created_at")
    .Skip(20)
    .Take(10)
    .ToListAsync();

// Complex filtering
var recentUsers = await client.Query<User>("users")
    .Where("created_at > ?", DateTime.UtcNow.AddDays(-7))
    .Where("is_active = ?", true)
    .OrderByDescending("created_at")
    .Take(50)
    .ToListAsync();

// Aggregates
var userCount = await client.Query<User>("users")
    .Where("email LIKE ?", "%@example.com")
    .CountAsync();

var hasUsers = await client.Query<User>("users")
    .Where("is_active = ?", true)
    .AnyAsync();

// Single result
var user = await client.Query<User>("users")
    .Where("id = ?", 123)
    .SingleOrDefaultAsync();

3. Direct SQL Queries with Type Mapping

// Query all users
var users = await client.QueryAsync<User>("SELECT * FROM users");

// Query with named parameters
var activeUsers = await client.QueryAsync<User>(
    "SELECT * FROM users WHERE is_active = @active",
    new { active = true }
);

// Get single user
var user = await client.QueryFirstOrDefaultAsync<User>(
    "SELECT * FROM users WHERE id = @id",
    new { id = 123 }
);

Query Builder API

Filtering

// Single WHERE clause with positional parameters
.Where("age > ?", 18)

// Multiple WHERE clauses (combined with AND)
.Where("age > ?", 18)
.Where("country = ?", "US")

// LIKE queries
.Where("email LIKE ?", "%@example.com")

// Complex conditions
.Where("(age > ? OR premium = ?) AND country = ?", 18, true, "US")

Sorting

// Single column ascending
.OrderBy("name")

// Single column descending
.OrderByDescending("created_at")

// Multiple columns
.OrderBy("country")
.ThenBy("city")
.ThenByDescending("created_at")

Pagination

// Skip first 20, take next 10
.Skip(20)
.Take(10)

// First page (10 per page)
.Take(10)

// Second page
.Skip(10)
.Take(10)

// Typical pagination pattern
int page = 2;
int pageSize = 10;
var results = await client.Query<User>("users")
    .OrderBy("id")
    .Skip((page - 1) * pageSize)
    .Take(pageSize)
    .ToListAsync();

Execution Methods

// Get all matching results
var list = await query.ToListAsync();

// Get first result or null
var first = await query.FirstOrDefaultAsync();

// Get exactly one result (throws if 0 or >1)
var single = await query.SingleAsync();

// Get exactly one result or null (throws if >1)
var singleOrNull = await query.SingleOrDefaultAsync();

// Get count of matching records
var count = await query.CountAsync();

// Check if any records match
var exists = await query.AnyAsync();

Select() Projection with Computed Properties (v1.2.1+)

Select specific columns and compute new values on-the-fly:

// DTO class for projection
public class UserSummary
{
    public int Id { get; set; }
    public string Name { get; set; }
    public bool IsAdult { get; set; }
    public int YearsUntil65 { get; set; }
}

// Boolean computed properties
var adults = await client.Query<User>("users")
    .Select(u => new UserSummary { 
        Id = u.Id, 
        Name = u.Name,
        IsAdult = u.Age >= 18,
        YearsUntil65 = 65 - u.Age
    })
    .ToListAsync();

// Math operations
var orderSummary = await client.Query<Order>("orders")
    .Select(o => new {
        o.Id,
        Total = o.Price * o.Quantity,
        Discount = o.Price * 0.1m,
        FinalPrice = (o.Price * o.Quantity) - (o.Price * 0.1m)
    })
    .ToListAsync();

// Comparisons and boolean logic
var userFlags = await client.Query<User>("users")
    .Select(u => new {
        u.Id,
        u.Name,
        u.Age,
        IsAdult = u.Age >= 18,
        IsMinor = u.Age < 18,
        IsSenior = u.Age >= 65,
        IsExpensive = u.MonthlyFee > 100
    })
    .ToListAsync();

// String methods
var formattedUsers = await client.Query<User>("users")
    .Select(u => new {
        u.Id,
        UpperName = u.Name.ToUpper(),
        LowerEmail = u.Email.ToLower()
    })
    .ToListAsync();

Supported Operations:

  • Comparisons: >, <, >=, <=, ==, !=
  • Math: +, -, *, /
  • Boolean logic: && (AND), || (OR), ! (NOT)
  • String methods: ToUpper(), ToLower(), Contains(), StartsWith(), EndsWith()

Advanced Usage

Custom Entity Mapper

Create a custom mapper for special mapping logic:

public class CustomUserMapper : IEntityMapper
{
    public T Map<T>(Dictionary<string, object?> row)
    {
        if (typeof(T) == typeof(User))
        {
            var user = new User
            {
                Id = Convert.ToInt32(row["user_id"]),
                Name = row["full_name"]?.ToString() ?? "",
                Email = row["email_address"]?.ToString()
            };
            return (T)(object)user;
        }
        throw new NotSupportedException($"Type {typeof(T)} not supported");
    }

    public IEnumerable<T> MapMany<T>(IEnumerable<Dictionary<string, object?>> rows)
    {
        return rows.Select(Map<T>);
    }
}

// Use custom mapper
var users = await client.QueryAsync<User>(
    "SELECT * FROM users",
    parameters: null,
    mapper: new CustomUserMapper()
);

// Or with query builder
var users = await client.Query<User>("users", new CustomUserMapper())
    .Where("is_active = ?", true)
    .ToListAsync();

Type Conversions

The default mapper automatically handles:

  • Primitives: int, long, decimal, float, double, bool, byte, short
  • Strings: Direct assignment
  • DateTime: Parsed from strings or numeric timestamps
  • Guid: Parsed from strings
  • Enums: Parsed from strings or integers
  • Nullable types: All of the above with ? suffix
  • SQLite booleans: Converts 0/1 to false/true
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; } = "";
    public decimal Price { get; set; }
    public DateTime? LastUpdated { get; set; }  // Nullable
    public ProductStatus Status { get; set; }   // Enum
    public bool IsActive { get; set; }          // SQLite stores as 0/1
}

public enum ProductStatus
{
    Draft,
    Published,
    Archived
}

Column Name Mapping

The default mapper automatically converts snake_case column names to PascalCase properties:

  • user_idUserId
  • email_addressEmailAddress
  • created_atCreatedAt
  • is_activeIsActive
// Database columns: user_id, full_name, email_address, created_at
public class User
{
    public int UserId { get; set; }        // Maps from user_id
    public string FullName { get; set; }   // Maps from full_name
    public string EmailAddress { get; set; } // Maps from email_address
    public DateTime CreatedAt { get; set; }  // Maps from created_at
}

Performance

  • Reflection caching: Property info is cached using ConcurrentDictionary
  • Mapping cache: Column-to-property mappings are cached per type
  • Minimal overhead: <1ms for 1000 rows on typical hardware
  • Lazy execution: Queries are only executed when you call an execution method

Performance Tips

  1. Reuse mappers: Create one mapper instance and reuse it
  2. Use Take() for limits: Reduces data transfer and processing
  3. Project only needed columns: SELECT id, name instead of SELECT *
  4. Use CountAsync() for counts: More efficient than .ToListAsync().Count()
  5. Use AnyAsync() for existence checks: More efficient than checking count

Examples

Pagination with Total Count

var query = client.Query<Product>("products")
    .Where("category = ?", "Electronics");

var total = await query.CountAsync();
var page1 = await query.Skip(0).Take(20).ToListAsync();

Console.WriteLine($"Showing {page1.Count()} of {total} products");

Search with Multiple Filters

var searchTerm = "%laptop%";
var minPrice = 500m;
var maxPrice = 2000m;

var results = await client.Query<Product>("products")
    .Where("name LIKE ?", searchTerm)
    .Where("price >= ?", minPrice)
    .Where("price <= ?", maxPrice)
    .Where("is_active = ?", true)
    .OrderBy("price")
    .ToListAsync();

Complex Joins (using direct SQL)

var ordersWithCustomers = await client.QueryAsync<OrderWithCustomer>(@"
    SELECT 
        o.id as order_id,
        o.total as order_total,
        c.id as customer_id,
        c.name as customer_name
    FROM orders o
    INNER JOIN customers c ON o.customer_id = c.id
    WHERE o.created_at > @since
    ORDER BY o.created_at DESC
    ",
    new { since = DateTime.UtcNow.AddDays(-30) }
);

What's New

v1.2.1 - Computed Properties in Select()

  • Computed properties - Use expressions in projections: .Select(u => new { u.Name, IsAdult = u.Age >= 18 })
  • Math operations - Calculate values: Total = u.Price * u.Quantity, Discount = u.Price * 0.1m
  • Boolean expressions - Create flags: IsExpensive = u.Price > 100, IsMinor = u.Age < 18
  • String methods - Transform text: UpperName = u.Name.ToUpper()

v1.2.0 - Select() Projection

  • Select() projection - Select specific columns: .Select(u => new { u.Id, u.Name })
  • DTO mapping - Project to strongly-typed DTOs
  • Performance optimization - Reduce data transfer by selecting only needed columns

v1.1.0 - Expression Tree LINQ

  • Expression tree support - Type-safe queries: .Where(u => u.Age >= 18)
  • Lambda expressions - Full IntelliSense and compile-time checking
  • OrderBy expressions - .OrderBy(u => u.Name), .ThenBy(u => u.CreatedAt)

Coming Soon

  • 🚧 Include() for joins - Automatic join and nested object mapping
  • 🚧 GroupBy() support - Aggregate queries with grouping
  • 🚧 IQueryable<T> - Full deferred execution support
  • CloudflareD1.NET - Core D1 client (NuGet)
  • CloudflareD1.NET.Migrations - Schema migrations (coming soon)
  • CloudflareD1.NET.Testing - Testing helpers (coming soon)

License

MIT License - see LICENSE file for details

Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

Support

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  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 was computed.  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 was computed.  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. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos 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 CloudflareD1.NET.Linq:

Package Downloads
CloudflareD1.NET.CodeFirst

Code-First ORM for CloudflareD1.NET. Define your database schema using C# classes and attributes, with automatic migration generation. Includes DbContext pattern, entity relationships, and model-driven development similar to Entity Framework Core but optimized for Cloudflare D1 and SQLite.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.11.0 191 10/28/2025
1.10.1 257 10/27/2025
1.10.0 162 10/27/2025
1.9.0 163 10/27/2025
1.9.0-beta 160 10/27/2025
1.8.0 168 10/27/2025
1.8.0-beta 165 10/27/2025
1.7.0 158 10/26/2025
1.7.0-beta 164 10/26/2025
1.6.0 165 10/26/2025
1.5.0 163 10/26/2025
1.4.0 151 10/26/2025
1.3.0 154 10/26/2025
1.2.1 158 10/26/2025
1.2.0 150 10/26/2025
1.1.0 156 10/26/2025
1.0.1-beta 120 10/26/2025
1.0.0 129 10/25/2025

v1.2.1: Computed Properties in Select() - Added support for computed properties in projections (e.g., IsAdult = u.Age >= 18, Total = u.Price * u.Quantity). SelectExpressionVisitor now captures parameters from computed expressions. DefaultEntityMapper enhanced to convert SQLite's 0/1 to Boolean. Supports boolean expressions, math operations (+ - * /), comparisons (< > <= >= == !=), and string methods (ToUpper, ToLower). 6 new unit tests (109 total). Fully backward compatible.