Existence Checks
Existence check methods allow you to efficiently test whether rows matching specific conditions exist in your database using optimized EXISTS and NOT EXISTS SQL patterns.
Overview
Available Methods:
- AnyAsync(predicate) - Check if any rows match a condition
- AllAsync(predicate) - Check if all rows match a condition
Both methods use SQL EXISTS patterns for optimal performance, avoiding the need to fetch and count rows.
Existence check methods with predicates were introduced in v1.8.0.
AnyAsync - Check for Matching Rows
Returns true if at least one row matches the specified condition.
Basic Usage
// Check if any users are over 35
var hasOldUsers = await client.Query<User>("users")
.AnyAsync(u => u.Age > 35);
if (hasOldUsers)
{
Console.WriteLine("Found users over 35");
}
Generated SQL:
SELECT EXISTS(SELECT 1 FROM users WHERE Age > ?)
With Existing Filters
Combines with existing Where() clauses:
// Check if any active users are under 25
var hasYoungActive = await client.Query<User>("users")
.Where(u => u.IsActive)
.AnyAsync(u => u.Age < 25);
Generated SQL:
SELECT EXISTS(
SELECT 1 FROM users
WHERE IsActive = ? AND Age < ?
)
Complex Predicates
// Check if any users match multiple conditions
var hasPremiumOrYoung = await client.Query<User>("users")
.AnyAsync(u => u.IsPremium || u.Age < 25);
// With compound conditions
var hasSpecialUsers = await client.Query<User>("users")
.AnyAsync(u => u.Age > 25 && u.IsActive && u.Email != null);
AllAsync - Check All Rows Match
Returns true if all rows match the specified condition. Uses NOT EXISTS with a negated predicate for efficiency.
Basic Usage
// Check if all users are adults (over 18)
var allAdults = await client.Query<User>("users")
.AllAsync(u => u.Age > 18);
if (allAdults)
{
Console.WriteLine("All users are adults");
}
Generated SQL:
SELECT NOT EXISTS(
SELECT 1 FROM users
WHERE NOT (Age > ?)
)
With Existing Filters
// Check if all active users are verified
var allActiveVerified = await client.Query<User>("users")
.Where(u => u.IsActive)
.AllAsync(u => u.IsVerified);
Generated SQL:
SELECT NOT EXISTS(
SELECT 1 FROM users
WHERE IsActive = ? AND NOT (IsVerified = ?)
)
Complex Predicates
// Check if all users meet complex criteria
var allMeetCriteria = await client.Query<User>("users")
.AllAsync(u => u.Age >= 18 && u.Email != null);
// With OR conditions
var allActiveOrPremium = await client.Query<User>("users")
.AllAsync(u => u.IsActive || u.IsPremium);
Performance Benefits
Why EXISTS is Fast
The EXISTS pattern is optimized because:
- Stops at first match - Doesn't need to scan all rows
- No data transfer - Only returns true/false
- Index-friendly - Can use indexes efficiently
// ✅ Efficient - Uses EXISTS, stops at first match
var hasOldUsers = await client.Query<User>("users")
.AnyAsync(u => u.Age > 35);
// ❌ Inefficient - Fetches all matching rows
var users = await client.Query<User>("users")
.Where(u => u.Age > 35)
.ToListAsync();
var hasOldUsers = users.Any(); // Wasteful if you only need true/false
Comparison with Count
// ✅ Fast - EXISTS stops at first match
var exists = await query.AnyAsync(u => u.Age > 35);
// ❌ Slower - COUNT scans all matching rows
var count = await query.Where(u => u.Age > 35).CountAsync();
var exists = count > 0;
Common Patterns
Validation Checks
// Validate username isn't taken
var usernameTaken = await client.Query<User>("users")
.AnyAsync(u => u.Username == newUsername);
if (usernameTaken)
{
throw new ValidationException("Username already exists");
}
Conditional Logic
// Check prerequisites before processing
var hasPendingOrders = await client.Query<Order>("orders")
.Where(o => o.UserId == userId)
.AnyAsync(o => o.Status == "Pending");
if (hasPendingOrders)
{
await ProcessPendingOrders(userId);
}
Data Integrity Checks
// Ensure all required fields are populated
var allHaveEmail = await client.Query<User>("users")
.Where(u => u.IsActive)
.AllAsync(u => u.Email != null && u.Email != "");
if (!allHaveEmail)
{
await SendEmailReminderToAdmins();
}
Business Rule Validation
// Check if all orders are fulfilled
var allOrdersFulfilled = await client.Query<Order>("orders")
.Where(o => o.CustomerId == customerId)
.AllAsync(o => o.Status == "Fulfilled");
if (allOrdersFulfilled)
{
await SendCompletionEmail(customerId);
}
Expression Support
Comparison Operators
// Greater than / Less than
await query.AnyAsync(u => u.Age > 25);
await query.AnyAsync(u => u.Age >= 18);
await query.AnyAsync(u => u.Age < 65);
await query.AnyAsync(u => u.Age <= 100);
// Equality
await query.AnyAsync(u => u.Status == "Active");
await query.AnyAsync(u => u.Id != excludedId);
Logical Operators
// AND conditions
await query.AnyAsync(u => u.Age > 18 && u.IsVerified);
// OR conditions
await query.AnyAsync(u => u.IsPremium || u.Age < 25);
// NOT conditions
await query.AllAsync(u => !u.IsBlocked);
// Complex combinations
await query.AnyAsync(u =>
(u.Age > 25 && u.IsActive) ||
(u.IsPremium && u.IsVerified));
String Operations
// String equality
await query.AnyAsync(u => u.Name == "John");
// String methods (when supported by expression visitor)
await query.AnyAsync(u => u.Email.Contains("@example.com"));
await query.AnyAsync(u => u.Name.StartsWith("A"));
await query.AnyAsync(u => u.Email.EndsWith(".com"));
Null Checks
// Check for null
await query.AnyAsync(u => u.Email != null);
await query.AllAsync(u => u.ProfilePictureUrl != null);
// Nullable value types
await query.AnyAsync(u => u.DeletedAt == null); // Not deleted
await query.AnyAsync(u => u.LastLoginAt != null); // Has logged in
Best Practices
1. Use AnyAsync Instead of Count > 0
// ✅ Good - Stops at first match
var exists = await query.AnyAsync(u => u.Age > 35);
// ❌ Bad - Counts all matching rows
var count = await query.Where(u => u.Age > 35).CountAsync();
var exists = count > 0;
2. Combine Filters for Efficiency
// ✅ Good - Single efficient query
var hasMatch = await client.Query<User>("users")
.Where(u => u.IsActive)
.Where(u => u.Country == "US")
.AnyAsync(u => u.Age > 35);
// ❌ Less efficient - Multiple queries or in-memory filtering
var activeUS = await client.Query<User>("users")
.Where(u => u.IsActive && u.Country == "US")
.ToListAsync();
var hasMatch = activeUS.Any(u => u.Age > 35);
3. Use AllAsync for Validation
// ✅ Good - Efficient validation
var allValid = await query.AllAsync(u => u.Email != null);
if (!allValid)
{
throw new ValidationException("All users must have email");
}
4. Early Return Pattern
// ✅ Good - Early return saves processing
public async Task<bool> CanProcessOrder(int orderId)
{
// Quick checks first
var orderExists = await orders.AnyAsync(o => o.Id == orderId);
if (!orderExists) return false;
var hasInventory = await inventory.AnyAsync(i => i.OrderId == orderId);
if (!hasInventory) return false;
// ... more checks
return true;
}
Difference from Parameterless AnyAsync()
CloudflareD1.NET has two versions of AnyAsync():
// Without predicate - checks if query has any results
var hasUsers = await client.Query<User>("users")
.Where(u => u.IsActive)
.AnyAsync(); // Returns true if any active users exist
// With predicate - adds additional condition (v1.8.0+)
var hasOldUsers = await client.Query<User>("users")
.Where(u => u.IsActive)
.AnyAsync(u => u.Age > 35); // Returns true if any active users over 35
Both are efficient and use optimized SQL patterns.
Error Handling
try
{
var hasMatch = await client.Query<User>("users")
.AnyAsync(u => u.Age > 35);
}
catch (ArgumentNullException ex)
{
// Predicate cannot be null
Console.WriteLine("Invalid predicate");
}
catch (D1QueryException ex)
{
// Database query error
Console.WriteLine($"Query failed: {ex.Message}");
}
See Also
- Query Builder - Basic query operations
- Set Operations - Union, Intersect, Except
- Expression Trees - Understanding expression translation