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.
Searching
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.
Indexing
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.
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. Let’s remember we need to index all searchable attributes of our items first. We can define our indexes via configuration, programmatically, or with a combination of both approaches. And let’s only index the properties we need in our queries. Indexes have memory and performance costs.
For more details on how to create indexes and how SQL queries works, check the Indexing guide and SQL Query: Behavior and Usage Overview.