Every enterprise nowadays requires data to manage its operations. The greatest solution for storing a well-organized collection of data is a database. Similarly, the most used computer language for storing and retrieving data in databases is SQL (Structured Query Language). In such a fast-paced modern world, it is crucial to be able to locate information quickly and effectively.
NCache fulfills this need by specifying the relevant key where the primary key is used to find data. Additionally, NCache also employs indexing in many modules to improve search performance on the cache and keeps track of it. As in real-world situations, you require flexible search capabilities to be able to locate information using criteria other than the primary key.
However, all searchable attributes should be indexed for searching. The extra computational storage needed to store the index as well as the time needed for an update to occur is balanced with the time saved during information retrieval.
Key Takeaways
Core Purpose of Indexing: To enable flexible, SQL-like searches using criteria beyond the primary key, NCache requires predefined query indexes. This balances the storage needed for the index map against drastically reduced data retrieval times.
Class-Wide vs. Selective Mapping: The [QueryIndexable] attribute automatically indexes all public fields within a class, whereas the [QueryIndexed] attribute targets only specific primitive properties to optimize performance and save memory.
Custom Cross-Language Aliases: The [QueryIndexed(“indexName”)] constructor allows you to assign explicit names to your indexed fields. This ensures that application code written in different development languages can target the exact same index fields.
Excluding Unneeded Fields: The [NonQueryIndexed] attribute can be combined with class-level indexing to deliberately block specific properties from being indexed, keeping your distributed cache’s memory footprint lean.
How to Define Indexes: Static, Dynamic, and Programmatic Approaches
To define indexes, NCache provides static and dynamic indexing. In both cases, the query indexes for a custom class must be predefined in order to search for items of that class in the cache. By doing this, the cache stores information in a way that makes it simple to query. You can specify static indexes through configuration or by defining indexes programmatically. Runtime indexes do not require any configuration before being used.
Before looking at the code implementation, here is a quick look at how attribute-based programmatic indexing compares to NCache’s configuration and runtime alternatives:
| Feature | Static Indexing (Configuration-Based) | Programmatic Indexing (Attribute-Based) | Runtime Data Indexing (Groups & Tags) |
|---|---|---|---|
| How It Is Defined | Explicitly mapping fields via NCache Management Center or config.ncconf. | Annotating your class domain model with [QueryIndexable] or [QueryIndexed]. | Associating string metadata (Categories/Tags) directly to a CacheItem at runtime. |
| Developer Ownership | Typically split between developers writing queries and DevOps/Admins configuring the cluster. | Completely owned by the developer directly inside the application’s C# or Java codebase. | Handled entirely at runtime through application logic execution. |
| Index Mapping Level | Maps explicit public fields or object properties via management tools. | Highly precise property-level tracking; supports custom alias tagging via [QueryIndexed(“alias”)]. | Non-property specific; indexes items globally by logical boundaries or key strings. |
| Assembly Handling | Requires manually uploading the compiled target assembly to NCache Management Center. | Automatically discovered by NCache when the annotated class object is registered. | No assembly mapping or structural type reflection needed by the cluster. |
| Primary Use Case | Legacy systems, strict production environments, or pre-existing compiled domain models. | Code-first architectures, modern microservices, and rapid application development. | Ad-hoc logical categorization, grouping, or multi-tenant indexing (e.g., grouping by region). |
How to Query NCache Indexes Programmatically (C# Code Implementation)
NCache offers the users with versatility to build indexes using several methods. You can generate indexes for specific fields or the entire class depending on your needs. NCache enables you to query indexes programmatically using the following annotations:
- QueryIndexable
- QueryIndexed
- QueryIndexed[“indexName”]
- NonQueryIndexed
Configure Query Index for the Class
Using the attribute QueryIndexable, you can index your class. It is defined at the class level to signify that the entire class can be indexed. All of the public properties and fields will be automatically indexed when a class is marked as QueryIndexable. If a private field in your class needs to be indexed, you must explicitly indicate it with the QueryIndexed annotation.
The following example demonstrates indexing the fields of the class named Product using QueryIndexable attribute. This will index the ID, Name, Quantity, ManufacturerName, and UnitPrice.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
[QueryIndexable] class Product { public int ID { get; set; } public string Name { get; set; } public string Quantity { get; set; } public string ManufacturerName { get; set; } public Decimal UnitPrice { get; set; } } |
The objects of the class that are indexed are added in the cache. The code example below shows how you can get the object of the Product class mentioned above through query.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
// Pre-conditions: Cache is already connected // Products are already present in the cache // Provide Fully Qualified Name (FQN) of your Product class // Create a query string query = "SELECT $Value$ FROM FQN.Product WHERE Quantity < ?"; // Use QueryCommand for query execution QueryCommand queryCommand = new QueryCommand(query); // Providing parameters for query queryCommand.Parameters.Add("Quantity", 500); // Executing QueryCommand through ICacheReader ICacheReader reader = cache.SearchService.ExecuteReader(queryCommand); // Checking if the result set is not empty if (reader.FieldCount > 0) { // Getting all Products with Quantity less then 500 while (reader.Read()) { Product product = reader.GetValue<Product>(1); // Perform required operations } } else { // Null query result set retrieved } |
Only when you wish to query index all or the majority of a class’s attributes or fields should you utilize this strategy. Otherwise, it is discouraged because it’ll potentially index characteristics that aren’t necessary and will end up consuming extra memory.
Configure Query Indexes for a Particular Property
When you need to index only a few properties or fields of a class, use QueryIndexed attribute since it saves performance and memory costs. To indicate that a marked property or field can be indexed, it is defined at the primitive property or field level. The fields or attributes with annotations are automatically indexed.
The class Flight demonstrated below shows how to index the desired attributes using QueryIndexed without indexing the whole class. Just for the fields ID and TotalPassengers, NCache will automatically create indexes.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Flight { [QueryIndexed] public int ID { get; set; } [QueryIndexed] public int TotalPassangers { get; set; } public string FlightType; private string DepartAirport { get; set; } public AirLine Airline { get; set; } } |
When only a small number of the properties or fields in your class need to be indexed, you can use this method to construct indexes. It gives you the option to select the desired properties or fields and index only those. This annotation saves you from the memory issue that may arise as a result of querying the entire class whereas you only require to query some attributes from it.
User-provided Query Index Configuration
Since utilizing the QueryIndexed property for selective indexing results in this default behavior- the index name is the same as the field name. Through the QueryIndexed(“indexName”) annotation, NCache does allow you to set unique names for your fields or properties that will be indexed. If so, an index is created using the user-specified name from the constructor. When writing application code in multiple languages with potentially distinct field names, this can be helpful.
In the class Employee, the fields EmployeeId, DateOfBirth, and Salary will be indexed as they are marked with the QueryIndexed attribute. However, to create an index encompassing the fields, EmployeeId and DateOfBirth, user-specified name in the constructor “ID” and “DOB” will be used.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class Employee { public string Name { get; set; } [QueryIndexed("ID")] public int EmployeeId { get; set; } [QueryIndexed("DOB")] public string DateOfBirth { get; set; } [QueryIndexed] public double Salary { get; set; } public string Position { get; set; } } |
Excluding Properties from Caching Indexes Using [NonQueryIndexed]
When your class is indexed, the NonQueryIndexed attribute can be used to stop the indexing of optional properties or fields. When a class is declared as QueryIndexable, the attribute NonQueryIndexed is defined at the level of the primitive property or field to specify that the property or field should not be indexed. The public fields and properties will all be automatically indexed. The NonQueryIndexed attribute indicates which fields should not be indexed.
The following example demonstrates how to explicitly declare the field CustomerName as NonQueryIndexed while the Order class is indexed. This will disregard the indexing of the annotated field.
|
1 2 3 4 5 6 7 8 9 10 11 12 |
[QueryIndexable] class Order { public int OrderId { get; set; } public DateTime OrderDate { get; set; } public string OrderDestination { get; set; } [NonQueryIndexed] public string CustomerName { get; set; } } |
The field CustomerName will not be added in the cache. Upon querying, you will get the value of CustomerName as null, whereas you have to retrieve all other fields explicitly.
When all but a few of the properties need to be indexed, you can combine QueryIndexable with NonQueryIndexed. This is potentially useful when you have a large data and you want to exclude only a small portion so that performance remains intact.
Conclusion
NCache provides a fast and efficient mechanism that lets you query your index according to the established criteria. And with this feature, you don’t have to worry about memory issues that might result from query indexing the entire class since you only need to index a few of its fields. Undoubtedly, NCache’s different query index annotations are a real blessing. You can check out other NCache features from our website.
Frequently Asked Questions (FAQ)
Q: Can NCache programmatically index non-primitive or complex reference types within a class?
A: No. Programmatic index annotations can only be applied to primitive properties, fields, and types (e.g., integers, strings, decimals, DateTime). Complex reference objects or custom types pointing to another class cannot be query-indexed and will be completely ignored by the query engine during cache operations.
Q: What happens to private fields when a class is marked with the [QueryIndexable] attribute?
A: By default, the [QueryIndexable] attribute automatically indexes only the public properties and fields of a class. If your domain logic requires a private field to be searchable via SQL queries, you must explicitly mark that specific private field with the [QueryIndexed] annotation inside the indexable class.
Q: What value is returned if you execute a SQL search query that targets a property marked [NonQueryIndexed]?
A: If a public property is explicitly excluded from indexing using the [NonQueryIndexed] attribute, its structural data is omitted from the internal query map. Running a SQL syntax search filter against that specific field will result in a null query response, meaning the field cannot be evaluated as part of your search criteria.
Q: Why does selective programmatic indexing via [QueryIndexed] improve cache performance compared to class-wide indexing?
A: Every index maintained by NCache requires extra computational storage and introduces processing overhead during cache write and update operations. Limiting index generation to only the properties frequently used in your SQL WHERE clauses saves memory and prevents the cluster from maintaining unnecessary tracking maps for unsearched fields.






