Search Client
Orleans.Search adds the Search<TGrain>() extension method to Orleans’s IClusterClient, enabling you to query grains by their state properties.
Getting the Client
Inject IClusterClient from the dependency injection container:
public class MyService
{
private readonly IClusterClient _client;
public MyService(IClusterClient client)
{
_client = client;
}
}Or resolve it directly:
var client = serviceProvider.GetRequiredService<IClusterClient>();The Search Method
The Search<TGrain>() method starts a fluent query for a specific grain type:
// Start a query for IUserGrain
var query = client.Search<IUserGrain>();
// Chain with Where and execution methods
var users = await client.Search<IUserGrain>()
.Where(u => u.IsActive == true)
.ToListAsync();Query Flow
client.Search<TGrain>() // Start query
.Where(predicate) // Add filter(s)
.ToListAsync() // Execute and return resultsComplete Example
public class ProductService
{
private readonly IClusterClient _client;
public ProductService(IClusterClient client)
{
_client = client;
}
// Find a single product
public async Task<IProductGrain?> FindByNameAsync(string name)
{
return await _client.Search<IProductGrain>()
.Where(p => p.Name == name)
.FirstOrDefaultAsync();
}
// Find products in a category
public async Task<List<IProductGrain>> GetByCategoryAsync(string category)
{
return await _client.Search<IProductGrain>()
.Where(p => p.Category == category)
.ToListAsync();
}
// Find products in price range
public async Task<List<IProductGrain>> GetInPriceRangeAsync(decimal min, decimal max)
{
return await _client.Search<IProductGrain>()
.Where(p => p.Price >= min && p.Price <= max)
.ToListAsync();
}
// Count in-stock products
public async Task<int> CountInStockAsync()
{
return await _client.Search<IProductGrain>()
.Where(p => p.InStock == true)
.CountAsync();
}
// Check if any products exist
public async Task<bool> AnyCategoryAsync(string category)
{
return await _client.Search<IProductGrain>()
.Where(p => p.Category == category)
.AnyAsync();
}
}Standard Grain Operations
Since Search is an extension method on IClusterClient, you use the same client for both standard grain operations and search:
public class UserService
{
private readonly IClusterClient _client;
public UserService(IClusterClient client)
{
_client = client;
}
// Standard Orleans: Get grain by ID
public IUserGrain GetUser(string userId)
{
return _client.GetGrain<IUserGrain>(userId);
}
// Orleans.Search: Find grains by state
public async Task<IUserGrain?> FindByEmailAsync(string email)
{
return await _client.Search<IUserGrain>()
.Where(u => u.Email == email)
.FirstOrDefaultAsync();
}
}Type Safety
The source generator creates type-safe Where extensions for each searchable grain type. Only [Queryable] properties can be used in predicates:
[Searchable(typeof(IUserGrain))]
public class UserState
{
[Queryable]
public string Email { get; set; }
// Not queryable
public string InternalNotes { get; set; }
}
// Works - Email is queryable
var users = await client.Search<IUserGrain>()
.Where(u => u.Email == "test@example.com")
.ToListAsync();
// Compile error - InternalNotes is not queryable
// var users = await client.Search<IUserGrain>()
// .Where(u => u.InternalNotes == "something")
// .ToListAsync();Next Steps
- Where Expressions - Supported filter expressions
- Async Methods - Execution methods like ToListAsync