NCache is a key-value store. It quickly retrieves cached items by their key. But it can also search cached entries by other properties. For example, instead of searching cached products by their key, we can also search them by category, price range, and weight. Let’s learn how to index cached items to make them searchable.
Key Takeaways
Mandatory Indexing Rule: NCache requires pre-configured indexes to search data by properties. Without indexing searchable attributes first, executing non-key SQL-like queries is impossible.
Supported SQL Operations: NCache supports native SQL-like syntax exclusively for SELECT and DELETE queries to search or clear cached data. It does not support INSERT or UPDATE statements.
Static vs. Dynamic Indexing: Indexes can be defined prior to deployment via configuration/code attributes ([QueryIndexed]), or handled dynamically at runtime using NCache Groups, Tags, or Named Tags.
Performance Trade-off: While indexes prevent slow, cluster-wide scans and ensure real-time query speeds, they carry memory and write-performance costs. Only index the specific attributes required by your search queries.
Querying Cached Data via SQL-like Syntax
Apart from finding items by their key, with NCache, we can query cached entries using a SQL-like syntax. We can write SELECT and DELETE queries to find and delete entries. NCache doesn’t support INSERT or UPDATE queries. Like a database SELECT query, we can retrieve projections, whole cached objects, or only keys. Also, NCache supports basic query and logical operators and aggregate functions.
For example, if we’re storing products, we can write a query like SELECT FROM Product WHERE UnitPrice <= 5.00` to find all cached products with prices less than 5. For more examples of how to search items and use operators and aggregate functions, check using SQL Queries with Distributed Cache.
Configuring Indexes for Attribute-Based Cache Searching
Unlike relational databases, to find items by other attributes, NCache requires indexes. Otherwise, it would have to scan the entire cache to find items and their related properties. It will make NCache slow. With indexes, our search operations are faster. To use a property of an object in a SQL-like search query, we need to index it first. We can index all public, private, and protected primitive fields and properties of an object. Let’s be aware that we can’t index reference-type fields or properties. Once NCache adds an item to an index, it’s returned as a result of a query if it meets the criteria of our search queries.
There are two mechanisms to define indexes with NCache: static and dynamic indexing.
| Indexing Strategy | Type | Best Used For | SQL Query Support |
|---|---|---|---|
| Static Indexing | Pre-defined | Known primitive fields/properties of custom objects using attributes | Full SELECT and DELETE |
| Group Index | Dynamic / Runtime | Logically partitioning or categorizing cached entries for efficiency | SELECT / DELETE via Groups |
| Tag Index | Dynamic / Runtime | Associating one or more string identifiers with items | SELECT / DELETE via Tags |
| Named Tag Index | Dynamic / Runtime | Advanced key-value attributes assigned dynamically at runtime | SELECT / DELETE via Named Tags |
Static Indexing
To search for custom objects, we need to index them first. We can define indexes either via configuration changes or programmatically.
Creating indexes via configuration
We can create indexes using the NCache Manager or a Powershell cmdlet. First, we should stop our cache, create our indexes, and restart it. The NCache Manager asks us to upload the assembly with our classes and choose what properties and fields to index. After that, NCache automatically indexes new entries. For more details about the NCache Manager and Powershell Add-QueryIndex cmdlet, check Configure Query Indexes.
Creating indexes programmatically
To create indexes programmatically, we need to annotate the fields of our objects to index them. NCache has the QueryIndexed attribute to index fields and properties.
For example, to index a product name and unit price, we need to annotate its Name and UnitPrice properties. Like this:
|
1 2 3 4 5 6 7 8 9 10 11 |
public class Product { public int ID { get; set } [QueryIndexed] public string Name { get; set } [QueryIndexed] public decimal UnitPrice { get; set } public decimal Weight { get; set } } |
By default, NCache names indexes after the annotated properties. But we can specify a different name with the QueryIndexed attribute. This feature is helpful when working with two client applications that use different names for the property we want to index. For example, if one application uses UnitPrice and another, pricePerUnit; we can use price as the index name in both applications and write our SQL-like search queries using price instead.
Apart from annotating individual properties, we can annotate classes with the QueryIndexable attribute. This way, NCache automatically indexes all public properties and fields. But we need to annotate private fields with QueryIndexed . And if we don’t want to index some properties, we need to annotate them with NonQueryIndexed. Let’s only index a class if we need to index all properties. But let’s adopt the approach of only indexing properties we need for searching since adding too many indexes has a memory and performance overhead.
For example, to index all properties of a product, excluding Weight, we need to annotate the class and the Weight property. Like this:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
[QueryIndexable] public class Product { public int ID { get; set } public string Name { get; set } public decimal UnitPrice { get; set } [NonQueryIndexed] public decimal Weight { get; set } } |
For more details on index annotations, check Define Indexes Programmatically.
Dynamic Indexing
We define some attributes of our cached entries at runtime using the NCache client. We can’t use annotations to index these attributes. For runtime attributes, NCache uses dynamic indexing. There are three types of dynamic indexes: group, tag, and named tag indexes.
Group Index
With Groups, we can logically partition our entries for efficiency. Groups work like logical categories. For example, we can use groups for our “most important customers” and “frequently purchased products.” We can retrieve and remove all entries belonging to the same group. Also, we can write SQL-like SELECT queries using groups. NCache automatically indexes groups. When we add an entry to a group that doesn’t exist, NCache creates an index for the group and stores all entries that belong to a group in the same index. To learn more about Groups, check Group Cache Data: An Overview.
Tag Index
Tags are string identifiers we associate with our entries. With Tags, we can better organize our data since we can retrieve and remove entries based on their tags. Unlike groups, we can associate one or more tags to our cache entries. For example, we can categorize our customers based on their location using “East Coast Customers” and “West Coast Customers” as tags. NCache supports searching and deleting entries using a tag name or the SQL-like query syntax. We can find all items that contain one or more tags, for example: for every new tag, NCache creates a tag index and saves all related cached items with that index.
Named Tag Index
Named Tags are enhanced Tags. Unlike Groups and Tags, that only support strings as identifiers, with Named Tags, we can associate primitive data types, strings, and dates to our entries. Named Tags are a list of key-value pairs attached to our entries at runtime.
For example, we can use Named Tags to store the discount we give to recurrent customers.
To search and delete entries using tag names or the SQL-like syntax, NCache automatically creates an index when we add entries with named tags that don’t exist. Also, NCache stores all related cached items in that index. Since Named Tag indexes support not only strings but all primitive types, we have the alternative to search for items with a broader range of data types.
To learn about Tags and Named Tags, check the Tag Cache Data and Named Tags with Cache Data guides.
Conclusion
Thanks to indexes and distributed queries, NCache offers real-time searching capabilities with a SQL-like syntax. Without indexes, NCache would have to scan the entire cache to find our entries. That’s why NCache needs indexes. To summarize the architectural rule: No Index = No SQL Search. While static and dynamic indexing unlock powerful real-time distributed queries across your cluster, remember to index only the specific attributes required by your application logic. Over-indexing introduces unnecessary memory overhead and can impact cache write performance.
For more details on how to create indexes and how SQL queries works, check the Indexing guide and SQL Query: Behavior and Usage Overview.
Frequently Asked Questions (FAQ)
Q: Can I index and search nested reference-type fields or custom objects in NCache?
A: No. NCache indexes apply strictly to public, private, and protected primitive fields and properties of an object (such as strings, numbers, and dates). Reference-type fields or complex nested properties cannot be indexed or used within your SQL-like query criteria.
Q: What is the functional difference between using a Tag and a Named Tag when querying data?
A: Standard Tags act purely as string identifiers, allowing you to associate one or more keywords with a cache item. Named Tags act as enhanced key-value pairs assigned at runtime, allowing you to attach and query a much broader range of data types, including primitive types and specific dates.
Q: How do [QueryIndexed] and [QueryIndexable] attributes behave differently when annotating a class?
A: The [QueryIndexed] attribute is applied manually to individual fields or properties that you want to index. Conversely, applying the class-level [QueryIndexable] attribute tells NCache to automatically index all public properties and fields within that class, unless you explicitly exclude them using the [NonQueryIndexed] attribute.
Q: Do I need to manually create an index before using Groups, Tags, or Named Tags at runtime?
A: No. Unlike static indexing which requires upfront setup via configuration or attributes, NCache handles dynamic indexes automatically. When you assign a new Group, Tag, or Named Tag to a cache entry at runtime, NCache implicitly creates the necessary underlying index if it does not already exist.





