CloudflareD1.NET.Linq
1.2.1
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
<PackageReference Include="CloudflareD1.NET.Linq" Version="1.2.1" />
<PackageVersion Include="CloudflareD1.NET.Linq" Version="1.2.1" />
<PackageReference Include="CloudflareD1.NET.Linq" />
paket add CloudflareD1.NET.Linq --version 1.2.1
#r "nuget: CloudflareD1.NET.Linq, 1.2.1"
#:package CloudflareD1.NET.Linq@1.2.1
#addin nuget:?package=CloudflareD1.NET.Linq&version=1.2.1
#tool nuget:?package=CloudflareD1.NET.Linq&version=1.2.1
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()andSkip()for paging - ✅ Aggregate functions -
CountAsync(),AnyAsync() - ✅ Nullable type support - Handles nullable properties correctly
- ✅ Custom mappers - Implement
IEntityMapperfor 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/1tofalse/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_id→UserIdemail_address→EmailAddresscreated_at→CreatedAtis_active→IsActive
// 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
- Reuse mappers: Create one mapper instance and reuse it
- Use
Take()for limits: Reduces data transfer and processing - Project only needed columns:
SELECT id, nameinstead ofSELECT * - Use
CountAsync()for counts: More efficient than.ToListAsync().Count() - 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
Related Packages
- 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 | Versions 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. |
-
.NETStandard 2.1
- CloudflareD1.NET (>= 1.0.2)
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.