Troubleshooting Guide
This guide covers common errors, their causes, and solutions when working with CloudflareD1.NET.
Connection Issues
401 Unauthorized
Error Message:
D1ApiException: D1 API request failed with status 401
Causes:
- Invalid API Token
- Expired API Token
- Incorrect token format
- Insufficient permissions
Solutions:
// 1. Verify token is correctly set
var options = new D1Options
{
ApiToken = "your-token-here", // Check for typos, extra spaces
AccountId = "your-account-id",
DatabaseId = "your-database-id"
};
// 2. Check token permissions in Cloudflare dashboard
// Required: Account → D1 → Edit (or Read for read-only)
// 3. Generate new token if expired
// https://dash.cloudflare.com/profile/api-tokens
// 4. Test token separately
var client = new D1Client(Options.Create(options), logger);
var health = await client.CheckHealthAsync();
if (!health.IsHealthy)
{
Console.WriteLine($"Error: {health.ErrorMessage}");
}
404 Not Found
Error Message:
D1ApiException: D1 API request failed with status 404
Causes:
- Invalid Account ID
- Invalid Database ID
- Database doesn't exist
- Wrong API endpoint
Solutions:
// 1. Verify Account ID
// Found at: https://dash.cloudflare.com/ → Select account → Copy ID from URL
var accountId = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"; // 32 characters
// 2. Verify Database ID
// Found at: D1 Dashboard → Select database → Copy UUID
var databaseId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"; // UUID format
// 3. List available databases to verify
var managementClient = client as ID1ManagementClient;
var databases = await managementClient.ListDatabasesAsync();
foreach (var db in databases.Result)
{
Console.WriteLine($"Database: {db.Name}, ID: {db.Uuid}");
}
// 4. Check API base URL
options.ApiBaseUrl = "https://api.cloudflare.com/client/v4/"; // Default
Network/Timeout Errors
Error Message:
D1ApiException: D1 API request timed out
HttpRequestException: Failed to communicate with D1 API
Causes:
- Network connectivity issues
- Firewall blocking outbound HTTPS
- Timeout too short for slow queries
- DNS resolution failure
Solutions:
// 1. Increase timeout for slow queries
services.AddCloudflareD1(options =>
{
options.TimeoutSeconds = 60; // Default: 30
});
// 2. Test network connectivity
var health = await client.CheckHealthAsync();
Console.WriteLine($"Latency: {health.LatencyMs}ms");
// 3. Check firewall allows HTTPS to api.cloudflare.com
// Port: 443
// 4. Enable detailed logging
builder.Logging.SetMinimumLevel(LogLevel.Debug);
// 5. Test with retry disabled to see raw errors
options.EnableRetry = false;
Rate Limiting (429)
Error Message:
D1ApiException: D1 API request failed with status 429
Cause: Too many requests to Cloudflare API
Solutions:
// 1. Retry is automatic (enabled by default)
options.EnableRetry = true;
options.MaxRetries = 5; // Increase for rate limits
// 2. Implement client-side throttling
private readonly SemaphoreSlim _throttle = new(10); // 10 concurrent requests
await _throttle.WaitAsync();
try
{
var result = await client.QueryAsync(sql);
}
finally
{
_throttle.Release();
}
// 3. Use batch operations to reduce request count
var statements = queries.Select(q => new D1Statement { Sql = q }).ToList();
await client.BatchAsync(statements); // Single request for multiple queries
// 4. Implement caching for frequently accessed data
_cache.GetOrCreateAsync($"key:{id}", async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(5);
return await client.QueryAsync<Entity>("SELECT * FROM entities WHERE id = ?", id);
});
Query Failures
SQL Syntax Errors
Error Message:
D1QueryException: SQL syntax error near "..."
Causes:
- Invalid SQL syntax
- SQLite vs standard SQL differences
- Missing/mismatched quotes
Solutions:
// 1. Test query locally first
var localOptions = new D1Options { UseLocalMode = true };
var localClient = new D1Client(Options.Create(localOptions), logger);
// 2. Use parameterized queries (prevents syntax issues)
// ❌ Bad - string concatenation
var sql = $"SELECT * FROM users WHERE name = '{name}'";
// ✅ Good - parameterized
var sql = "SELECT * FROM users WHERE name = ?";
var result = await client.QueryAsync(sql, new[] { name });
// 3. Check SQLite-specific syntax
// SQLite uses AUTOINCREMENT, not AUTO_INCREMENT
// SQLite uses INTEGER PRIMARY KEY, not SERIAL
// SQLite date functions: date('now'), datetime('now')
// 4. Enable debug logging to see actual SQL
builder.Logging.SetMinimumLevel(LogLevel.Debug);
Parameter Mismatch
Error Message:
Wrong number of parameters
Parameter count mismatch
Solutions:
// 1. Match parameter count
var sql = "SELECT * FROM users WHERE name = ? AND age = ?";
var params = new object[] { "John", 25 }; // 2 params for 2 placeholders
// 2. Use named parameters
var sql = "SELECT * FROM users WHERE name = @name AND age = @age";
var params = new { name = "John", age = 25 };
// 3. For LINQ queries, parameters are automatic
var users = await client.Query<User>("users")
.Where(u => u.Name == "John" && u.Age == 25)
.ToListAsync();
// 4. Check parameter array length
if (parameters is object[] arr)
{
Console.WriteLine($"Parameter count: {arr.Length}");
}
Large Result Sets
Error Message:
OutOfMemoryException
Response too large
Causes:
- Loading too many rows at once
- Large BLOB/TEXT columns
- Inefficient queries
Solutions:
// 1. Use pagination
var pageSize = 100;
var page = 0;
var users = await client.Query<User>("users")
.OrderBy(u => u.Id)
.Skip(page * pageSize)
.Take(pageSize)
.ToListAsync();
// 2. Use async streaming for large datasets
await foreach (var user in client.Query<User>("users").ToAsyncEnumerable())
{
// Process one at a time (memory efficient)
await ProcessUserAsync(user);
}
// 3. Project only needed columns
var names = await client.Query<User>("users")
.Select(u => new { u.Id, u.Name }) // Only 2 columns
.ToListAsync();
// 4. Add WHERE clause to filter
var activeUsers = await client.Query<User>("users")
.Where(u => u.IsActive)
.ToListAsync();
// 5. Check query performance
var result = await client.QueryAsync("SELECT * FROM large_table");
Console.WriteLine($"Duration: {result.Meta?.Duration}ms");
Console.WriteLine($"Rows: {result.Results?.Count}");
Migration Problems
Pending Migrations
Error Message:
Database schema out of sync
Pending migrations exist
Solutions:
// 1. Check migration status
var runner = new MigrationRunner(client, "Migrations");
var applied = await runner.GetAppliedMigrationsAsync();
var pending = runner.GetPendingMigrations();
Console.WriteLine($"Applied: {applied.Count}");
Console.WriteLine($"Pending: {pending.Count}");
// 2. Apply pending migrations
var appliedMigrations = await runner.MigrateAsync();
Console.WriteLine($"Applied {appliedMigrations.Count} migrations");
// 3. Rollback if needed
await runner.RollbackAsync("MigrationId");
// 4. Check migration history
var history = await client.QueryAsync("SELECT * FROM __migrations ORDER BY id DESC");
Migration Conflicts
Error Message:
Column already exists
Table already exists
Cannot drop column
Solutions:
// 1. Check current schema
var columns = await client.QueryAsync("PRAGMA table_info(tablename)");
// 2. Use IF NOT EXISTS
builder.CreateTable("users", t => t
.Integer("id").PrimaryKey().AutoIncrement()
.Text("name").NotNull()
).IfNotExists();
// 3. Handle dropped columns (SQLite requires table recreation)
// See: docs/migrations/advanced.md
// 4. Generate fresh migration
dotnet d1 add MyMigration
// 5. For Code-First, check model diff
var generator = new CodeFirstMigrationGenerator("Migrations");
var summary = await generator.GetChangesSummaryAsync(context);
Console.WriteLine(summary);
Rollback Failures
Error Message:
Cannot rollback: migration not found
Rollback failed: table in use
Solutions:
// 1. Check migration exists
var applied = await runner.GetAppliedMigrationsAsync();
if (applied.Any(m => m.Id == targetMigration))
{
await runner.RollbackAsync(targetMigration);
}
// 2. Close all connections before rollback (local mode)
await client.DisposeAsync();
var newClient = CreateNewClient();
await runner.RollbackAsync(targetMigration);
// 3. Check Down() method is implemented
public override void Down(MigrationBuilder builder)
{
builder.DropTable("users"); // Must be implemented
}
// 4. Manual rollback if needed
await client.ExecuteAsync("DROP TABLE IF EXISTS problem_table");
await client.ExecuteAsync("DELETE FROM __migrations WHERE id = 'migration-id'");
Performance Issues
Slow Queries
Symptoms: Queries taking >1 second
Solutions:
// 1. Add indexes
await client.ExecuteAsync(@"
CREATE INDEX IF NOT EXISTS idx_users_email
ON users(email)
");
// 2. Use EXPLAIN to analyze query plan
var explain = await client.QueryAsync("EXPLAIN QUERY PLAN SELECT * FROM users WHERE email = ?", email);
// 3. Avoid SELECT *
// ❌ Slow
var users = await client.QueryAsync("SELECT * FROM users");
// ✅ Fast
var users = await client.QueryAsync("SELECT id, name FROM users");
// 4. Use compiled queries for repeated queries
var compiledQuery = CompiledQuery.Create<User>(
"users",
q => q.Where(u => u.Age > 25)
);
// 95% faster on subsequent executions
// 5. Check query duration in logs
// Logged automatically at Information level
N+1 Query Problem
Symptom: Many queries in a loop
// ❌ N+1 Problem (1 + N queries)
var users = await client.Query<User>("users").ToListAsync();
foreach (var user in users)
{
var posts = await client.Query<Post>("posts")
.Where(p => p.UserId == user.Id)
.ToListAsync(); // Separate query for each user!
}
Solutions:
// ✅ Solution 1: Join
var userPosts = await client.Query<User>("users")
.Join(
client.Query<Post>("posts"),
user => user.Id,
post => post.UserId
)
.Select((user, post) => new { User = user, Post = post })
.ToListAsync();
// ✅ Solution 2: Load all, group in memory
var users = await client.Query<User>("users").ToListAsync();
var posts = await client.Query<Post>("posts").ToListAsync();
var userPosts = users.GroupJoin(posts, u => u.Id, p => p.UserId, (u, posts) => new { User = u, Posts = posts });
// ✅ Solution 3: Batch query with IN clause
var userIds = users.Select(u => u.Id).ToArray();
var posts = await client.Query<Post>("posts")
.Where(p => userIds.Contains(p.UserId))
.ToListAsync();
Memory Issues
Error Message:
OutOfMemoryException
Process consuming excessive memory
Solutions:
// 1. Use async streaming for large result sets
await foreach (var item in query.ToAsyncEnumerable())
{
await ProcessItemAsync(item);
// Item is garbage collected after processing
}
// 2. Use pagination
for (int page = 0; page < totalPages; page++)
{
var items = await query
.Skip(page * pageSize)
.Take(pageSize)
.ToListAsync();
await ProcessBatchAsync(items);
items.Clear(); // Allow GC
}
// 3. Project only needed columns
var ids = await client.Query<Entity>("entities")
.Select(e => e.Id) // Just one column
.ToListAsync();
// 4. Batch process with smaller chunks
var batch = new List<Entity>(100);
await foreach (var item in query.ToAsyncEnumerable())
{
batch.Add(item);
if (batch.Count >= 100)
{
await ProcessBatchAsync(batch);
batch.Clear();
}
}
Code-First Issues
Foreign Key Violations
Error Message:
FOREIGN KEY constraint failed
Causes:
- Inserting child before parent
- Deleting parent before children
- Invalid foreign key value
Solutions:
// 1. FK-aware ordering is automatic (v1.0.2+)
// Parents inserted before children automatically
context.Users.Add(user);
context.Posts.Add(new Post { UserId = user.Id, AuthorId = user.Id }); // Even if added first
await context.SaveChangesAsync(); // Correct order applied automatically
// 2. Manual ordering if needed
await context.Users.AddAsync(parent);
await context.SaveChangesAsync(); // Save parent first
await context.Posts.AddAsync(new Post { UserId = parent.Id });
await context.SaveChangesAsync(); // Then child
// 3. Check FK relationships in model
[ForeignKey("UserId")]
public User? User { get; set; } // Navigation property
[ForeignKey(nameof(UserId))] // Or use nameof
public int UserId { get; set; } // FK property
// 4. Enable cascade delete
builder.Entity<Post>()
.HasOne(p => p.User)
.WithMany(u => u.Posts)
.OnDelete(DeleteBehavior.Cascade);
Property Changes Not Detected
Symptom: Updates don't save
Causes:
- Entity not tracked
- Property not modified
- Snapshot not captured
Solutions:
// 1. Ensure entity is tracked
var user = await context.Users.FindAsync(1); // Tracked
user.Name = "Updated";
await context.SaveChangesAsync(); // Will update
// 2. Use Update() to track untracked entities
var user = new User { Id = 1, Name = "Updated" };
context.Users.Update(user); // Now tracked
await context.SaveChangesAsync();
// 3. Check entity state
var entry = context.ChangeTracker.GetEntry(user);
Console.WriteLine($"State: {entry.State}");
Console.WriteLine($"Modified properties: {string.Join(", ", entry.GetModifiedProperties())}");
// 4. Manual tracking if needed
context.ChangeTracker.TrackUpdate(user);
Snapshot Issues
Error Message:
Cannot generate migration: snapshot missing
Model changes not detected
Solutions:
// 1. Generate initial migration to create snapshot
dotnet d1 add-codefirst InitialCreate --context MyDbContext
// 2. Check snapshot file exists
// Location: Migrations/.migrations-snapshot.json
// 3. Regenerate snapshot if corrupted
// Delete Migrations/.migrations-snapshot.json
// Run: dotnet d1 add-codefirst RecreateSnapshot --context MyDbContext
// 4. Check model changes
var generator = new CodeFirstMigrationGenerator("Migrations");
var hasChanges = await generator.HasPendingChangesAsync(context);
var summary = await generator.GetChangesSummaryAsync(context);
Console.WriteLine(summary);
Debug Logging
Enable Detailed Logging
// Program.cs / Startup.cs
builder.Logging.AddConsole();
builder.Logging.SetMinimumLevel(LogLevel.Debug);
// Filter to D1 only
builder.Logging.AddFilter("CloudflareD1", LogLevel.Debug);
builder.Logging.AddFilter("Microsoft", LogLevel.Warning);
Example Debug Output:
dbug: CloudflareD1.NET.D1Client[0]
Executing query in remote mode: SELECT * FROM users
dbug: CloudflareD1.NET.Providers.CloudflareD1Provider[0]
Request payload: {"sql":"SELECT * FROM users","params":null}
dbug: CloudflareD1.NET.Providers.CloudflareD1Provider[0]
Response status: 200, Content: {"success":true,"result":[...],"meta":{...}}
info: CloudflareD1.NET.Providers.CloudflareD1Provider[0]
D1 query executed successfully, returned 5 result(s) (Duration: 123ms)
Request/Response Inspection
// Add HTTP logging middleware
builder.Services.AddHttpClient<D1Client>()
.AddHttpMessageHandler(() => new LoggingHandler(logger));
public class LoggingHandler : DelegatingHandler
{
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
// Log request
Console.WriteLine($"Request: {request.Method} {request.RequestUri}");
if (request.Content != null)
{
var content = await request.Content.ReadAsStringAsync();
Console.WriteLine($"Body: {content}");
}
var response = await base.SendAsync(request, cancellationToken);
// Log response
Console.WriteLine($"Response: {response.StatusCode}");
var responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Body: {responseContent}");
return response;
}
}
Getting Help
If you're still stuck:
- Check documentation: https://jdtoon.github.io/CloudflareD1.NET/
- Enable debug logging and examine output
- Test with health check:
await client.CheckHealthAsync() - Try local mode to isolate network/API issues
- Review examples: https://github.com/jdtoon/CloudflareD1.NET/tree/main/samples
- Create GitHub issue: https://github.com/jdtoon/CloudflareD1.NET/issues
Next Steps
- Production Deployment Guide - Best practices for production
- Performance Tuning Guide - Optimization techniques