Query APIAsync Methods

Async Methods

After building a query with Where, execute it with one of these async methods.

ToListAsync

Returns all matching grains as a list.

List<IUserGrain> users = await client.Search<IUserGrain>()
    .Where(u => u.IsActive == true)
    .ToListAsync();

Returns: List<TGrain> containing all matching grain references.

Use when: You need all matching grains and will process them individually.

// Get all active users and fetch their emails
var activeUsers = await client.Search<IUserGrain>()
    .Where(u => u.IsActive == true)
    .ToListAsync();
 
foreach (var user in activeUsers)
{
    var email = await user.GetEmailAsync();
    Console.WriteLine(email);
}

FirstOrDefaultAsync

Returns the first matching grain, or null if none match.

IUserGrain? user = await client.Search<IUserGrain>()
    .Where(u => u.Email == "alice@example.com")
    .FirstOrDefaultAsync();
 
if (user != null)
{
    var displayName = await user.GetDisplayNameAsync();
}

Returns: TGrain? - the first matching grain or null.

Use when: You expect zero or one match, or only need the first result.

// Find user by unique email
public async Task<IUserGrain?> FindByEmailAsync(string email)
{
    return await client.Search<IUserGrain>()
        .Where(u => u.Email == email)
        .FirstOrDefaultAsync();
}

FirstAsync

Returns the first matching grain. Throws if no grains match.

IUserGrain user = await client.Search<IUserGrain>()
    .Where(u => u.Email == "alice@example.com")
    .FirstAsync();

Returns: TGrain - the first matching grain.

Throws: InvalidOperationException if no grains match.

Use when: You expect at least one match and want an exception if there isn’t.

// Get admin user - throw if not found
public async Task<IUserGrain> GetAdminAsync()
{
    return await client.Search<IUserGrain>()
        .Where(u => u.Role == "admin")
        .FirstAsync();
}

CountAsync

Returns the number of matching grains.

int count = await client.Search<IUserGrain>()
    .Where(u => u.IsActive == true)
    .CountAsync();
 
Console.WriteLine($"Active users: {count}");

Returns: int - the count of matching grains.

Use when: You only need to know how many grains match, not the grains themselves.

// Count products by category
public async Task<Dictionary<string, int>> GetCategoryCountsAsync()
{
    var categories = new[] { "electronics", "clothing", "books" };
    var counts = new Dictionary<string, int>();
 
    foreach (var category in categories)
    {
        counts[category] = await client.Search<IProductGrain>()
            .Where(p => p.Category == category)
            .CountAsync();
    }
 
    return counts;
}

AnyAsync

Returns true if at least one grain matches.

bool hasActiveUsers = await client.Search<IUserGrain>()
    .Where(u => u.IsActive == true)
    .AnyAsync();
 
if (hasActiveUsers)
{
    Console.WriteLine("There are active users");
}

Returns: bool - true if any grains match, false otherwise.

Use when: You only need to know if matches exist, not how many or which ones.

// Check if email is already taken
public async Task<bool> IsEmailTakenAsync(string email)
{
    return await client.Search<IUserGrain>()
        .Where(u => u.Email == email)
        .AnyAsync();
}

Method Comparison

MethodReturnsThrows on EmptyUse Case
ToListAsync()List<TGrain>No (empty list)Get all matches
FirstOrDefaultAsync()TGrain?No (null)Get first or null
FirstAsync()TGrainYesGet first, expect match
CountAsync()intNoCount matches
AnyAsync()boolNoCheck existence

Performance Considerations

  1. Use AnyAsync() over CountAsync() > 0

    • AnyAsync() can stop at the first match
    • CountAsync() must count all matches
  2. Use FirstOrDefaultAsync() over ToListAsync().FirstOrDefault()

    • FirstOrDefaultAsync() only fetches one record
    • ToListAsync() fetches all matching records
  3. Use CountAsync() when you only need the count

    • Don’t fetch full grain references just to count them

Complete Example

public class ProductCatalogService
{
    private readonly IClusterClient _client;
 
    public ProductCatalogService(IClusterClient client)
    {
        _client = client;
    }
 
    // Get all products in a category
    public async Task<List<IProductGrain>> GetByCategory(string category)
    {
        return await _client.Search<IProductGrain>()
            .Where(p => p.Category == category)
            .ToListAsync();
    }
 
    // Find product by name
    public async Task<IProductGrain?> FindByName(string name)
    {
        return await _client.Search<IProductGrain>()
            .Where(p => p.Name == name)
            .FirstOrDefaultAsync();
    }
 
    // Get the first featured product (throws if none)
    public async Task<IProductGrain> GetFeaturedProduct()
    {
        return await _client.Search<IProductGrain>()
            .Where(p => p.IsFeatured == true)
            .FirstAsync();
    }
 
    // Count in-stock products
    public async Task<int> CountInStock()
    {
        return await _client.Search<IProductGrain>()
            .Where(p => p.InStock == true)
            .CountAsync();
    }
 
    // Check if category has products
    public async Task<bool> CategoryHasProducts(string category)
    {
        return await _client.Search<IProductGrain>()
            .Where(p => p.Category == category)
            .AnyAsync();
    }
}

Next Steps