Including Counters
-
Counters can be included when loading entities or when making queries.
-
The Session stores the included counters in its in-memory cache,
so their values can be accessed later in the same session without making additional requests to the server. -
To see how to include counters when creating a subscription, see Create subscription - include counters.
-
In this article:
Sample data
The examples in this article are based on the following sample data:
- Sample_counters
- Product_class
using (var session = store.OpenSession())
{
var documentCounters = session.CountersFor("products/1-A");
// Increase counter "Likes" by 1, or create it with a value of 1 if it doesn't exist
documentCounters.Increment("Likes");
// Increase counter "Dislikes" by 1, or create it with a value of 1 if it doesn't exist
documentCounters.Increment("Dislikes", 1);
// Increase counter "Downloads" by 15, or create it with a value of 15 if it doesn't exist
documentCounters.Increment("Downloads", 15);
session.SaveChanges();
}
public class Product
{
public string Id { get; set; }
public string Name { get; set; }
public string Supplier { get; set; }
public string Descripton { get; set; }
}
Include counters when loading
Include single counter:
Include a single counter using IncludeCounter():
- Load
- Loae_async
using (var session = store.OpenSession())
{
// Load document "products/1-A"
var product = session.Load<Product>("products/1-A", includeBuilder =>
// Call 'IncludeCounter', pass the name of the counter to include
includeBuilder.IncludeCounter("Likes"));
// The included counter is now cached in the session's memory.
// Getting its value will NOT trigger a server request
var productLikes = session
.CountersFor("products/1-A")
.Get("Likes");
Console.WriteLine("Number of likes: " + productLikes);
}
using (var asyncSession = store.OpenAsyncSession())
{
// Load document "products/1-A"
var product = await asyncSession.LoadAsync<Product>("products/1-A", includeBuilder =>
// Call 'IncludeCounter', pass the name of the counter to include
includeBuilder.IncludeCounter("Likes"));
// The included counter is now cached in the session's memory.
// Getting its value will NOT trigger a server request.
var productLikes = await asyncSession
.CountersFor("products/1-A")
.GetAsync("Likes");
Console.WriteLine("Number of likes: " + productLikes);
}
Include multiple counters:
Include multiple counters using IncludeCounters():
- Load
- Load_async
using (var session = store.OpenSession())
{
// Load document "products/1-A"
var product = session.Load<Product>("products/1-A", includeBuilder =>
// Call 'IncludeCounters', pass a list of the counter names to include
includeBuilder.IncludeCounters(new[] { "Likes", "Downloads" }));
// The included counters are now cached in the session's memory.
// Getting their values will NOT trigger a server request.
var productDownloads = session
.CountersFor("products/1-A")
.Get("Downloads");
Console.WriteLine("Number of downloads: " + productDownloads);
}
using (var asyncSession = store.OpenAsyncSession())
{
// Load document "products/1-A"
var product = await asyncSession.LoadAsync<Product>("products/1-A", includeBuilder =>
// Call 'IncludeCounters' and pass a list of counter names to include
includeBuilder.IncludeCounters(new[] { "Likes", "Downloads" }));
// The included counters are now cached in the session's memory.
// Getting their values will NOT trigger a server request.
var productDownloads = await asyncSession
.CountersFor("products/1-A")
.GetAsync("Downloads");
Console.WriteLine("Number of downloads: " + productDownloads);
}
You can also include multiple counters by chaining multiple IncludeCounter() calls:
- Load
- Load_async
using (var session = store.OpenSession())
{
// Load document "products/1-A"
var product = session.Load<Product>("products/1-A", includeBuilder =>
// Include multiple counters by chaining 'IncludeCounter' calls
includeBuilder
.IncludeCounter("Likes")
.IncludeCounter("Downloads"));
// The included counters are now cached in the session's memory.
// Getting their values will NOT trigger a server request.
var productDownloads = session
.CountersFor("products/1-A")
.Get("Downloads");
Console.WriteLine("Number of downloads: " + productDownloads);
}
using (var asyncSession = store.OpenAsyncSession())
{
// Load document "products/1-A"
var product = await asyncSession.LoadAsync<Product>("products/1-A", includeBuilder =>
// Include multiple counters by chaining 'IncludeCounter' calls
includeBuilder
.IncludeCounter("Likes")
.IncludeCounter("Downloads"));
// The included counters are now cached in the session's memory.
// Getting their values will NOT trigger a server request.
var productDownloads = await asyncSession
.CountersFor("products/1-A")
.GetAsync("Downloads");
Console.WriteLine("Number of downloads: " + productDownloads);
}
Include all counters:
Include ALL counters using IncludeAllCounters():
- Load
- Load_async
using (var session = store.OpenSession())
{
// Load document "products/1-A"
var product = session.Load<Product>("products/1-A", includeBuilder =>
// Call 'IncludeAllCounters' to include all counters
includeBuilder.IncludeAllCounters());
// All counters belonging to the document are now cached in the session's memory.
// Getting their values will NOT trigger a server request.
var productDislikes = session
.CountersFor("products/1-A")
.Get("Dislikes");
Console.WriteLine("Number of dislikes: " + productDislikes);
}
using (var asyncSession = store.OpenAsyncSession())
{
// Load document "products/1-A"
var product = await asyncSession.LoadAsync<Product>("products/1-A", includeBuilder =>
// Call 'IncludeAllCounters' to include all counters
includeBuilder.IncludeAllCounters());
// All counters belonging to the document are now cached in the session's memory.
// Getting their values will NOT trigger a server request.
var productDislikes = await asyncSession
.CountersFor("products/1-A")
.GetAsync("Dislikes");
Console.WriteLine("Number of dislikes: " + productDislikes);
}
Include counters when querying
Include single counter:
Use IncludeCounter() to include a single counter for each resulting document.
The counter with the specified name will be cached in the session's memory for each document returned.
- Query
- Query_async
- RawQuery
- RawQuery_async
- RQL
using (var session = store.OpenSession())
{
// Query for product documents
var products = session.Query<Product>()
// Filter documents as needed
.Where(x=> x.Supplier == "suppliers/1-A")
.Include(includeBuilder =>
// Include the "Likes" counter for each matching document
includeBuilder.IncludeCounter("Likes"))
.ToList();
foreach (var product in products)
{
// Access the counters included in the session
// Getting their values will NOT trigger a server request.
var productLikes = session.CountersFor(product).Get("Likes");
if (productLikes != null)
Console.WriteLine($"Product '{product.Id}' has {productLikes} likes.");
else
Console.WriteLine($"Product '{product.Id}' has no 'Likes' counter.");
}
}
using (var asyncSession = store.OpenAsyncSession())
{
// Query for product documents
var products = await asyncSession.Query<Product>()
// Filter documents as needed
.Where(x => x.Supplier == "suppliers/1-A")
.Include(includeBuilder =>
// Include the "Likes" counter for each matching document
includeBuilder.IncludeCounter("Likes"))
.ToListAsync();
foreach (var product in products)
{
// Access the counters included in the session
// Getting their values will NOT trigger a server request.
var productLikes = await asyncSession.CountersFor(product).GetAsync("Likes");
if (productLikes != null)
Console.WriteLine($"Product '{product.Id}' has {productLikes} likes.");
else
Console.WriteLine($"Product '{product.Id}' has no 'Likes' counter.");
}
}
using (var session = store.OpenSession())
{
var products = session.Advanced.RawQuery<Product>(@"
from Products as p
where p.Supplier == 'suppliers/1-A'
include counters(p, 'Likes')
")
.ToList();
foreach (var product in products)
{
var likes = session.CountersFor(product).Get("Likes");
if (likes != null)
Console.WriteLine($"Product '{product.Id}' has {likes} likes.");
else
Console.WriteLine($"Product '{product.Id}' has no 'Likes' counter.");
}
}
using (var asyncSession = store.OpenAsyncSession())
{
var products = await asyncSession.Advanced.AsyncRawQuery<Product>(@"
from Products as p
where p.Supplier == 'suppliers/1-A'
include counters(p, 'Likes')
")
.ToListAsync();
foreach (var product in products)
{
var likes = await asyncSession.CountersFor(product).GetAsync("Likes");
if (likes != null)
Console.WriteLine($"Product '{product.Id}' has {likes} likes.");
else
Console.WriteLine($"Product '{product.Id}' has no 'Likes' counter.");
}
}
from "Products"
where Supplier == "suppliers/1-A"
include counters("Likes")
Include multiple counters:
Use IncludeCounters() to include multiple counters for each resulting document.
Counters with the specified names will be cached in the session's memory for each document returned.
- Query
- Query_async
- RawQuery
- RawQuery_async
- RQL
using (var session = store.OpenSession())
{
// Query for product documents
var products = session.Query<Product>()
// Filter documents as needed
.Where(x=> x.Supplier == "suppliers/1-A")
.Include(includeBuilder =>
// Call 'IncludeCounters', pass a list of counter names to include
// Alternatively, you can chain individual calls:
// includeBuilder.IncludeCounter("Likes").IncludeCounter("Downloads")
includeBuilder.IncludeCounters(new[] { "Likes", "Downloads" }))
.ToList();
foreach (var product in products)
{
// Access the counters included in the session
// Getting their values will NOT trigger a server request.
var productLikes = session.CountersFor(product).Get("Likes");
var productDownloads = session.CountersFor(product).Get("Downloads");
}
}
using (var asyncSession = store.OpenAsyncSession())
{
// Query for product documents
var products = await asyncSession.Query<Product>()
// Filter documents as needed
.Where(x => x.Supplier == "suppliers/1-A")
.Include(includeBuilder =>
// Call 'IncludeCounters', pass a list of counter names to include
// Alternatively, you can chain individual calls:
// includeBuilder.IncludeCounter("Likes").IncludeCounter("Downloads")
includeBuilder.IncludeCounters(new[] { "Likes", "Downloads" }))
.ToListAsync();
foreach (var product in products)
{
// Access the counters included in the session
// Getting their values will NOT trigger a server request.
var productLikes = await asyncSession.CountersFor(product).GetAsync("Likes");
var productDownloads = await asyncSession.CountersFor(product).GetAsync("Downloads");
}
}
using (var session = store.OpenSession())
{
var products = session.Advanced.RawQuery<Product>(@"
from Products
where Supplier == 'suppliers/1-A'
include counters('Likes'), counters('Downloads')
")
.ToList();
foreach (var product in products)
{
var likes = session.CountersFor(product).Get("Likes");
var downloads = session.CountersFor(product).Get("Downloads");
}
}
using (var asyncSession = store.OpenAsyncSession())
{
var products = await asyncSession.Advanced.AsyncRawQuery<Product>(@"
from Products
where Supplier == 'suppliers/1-A'
include counters('Likes'), counters('Downloads')
")
.ToListAsync();
foreach (var product in products)
{
var likes = await asyncSession.CountersFor(product).GetAsync("Likes");
var downloads = await asyncSession.CountersFor(product).GetAsync("Downloads");
}
}
from "Products"
where Supplier = "suppliers/1-A"
include counters("Likes"), counters("Downloads")
Include all counters:
Use IncludeAllCounters() to include ALL counters.
All counters for each resulting document will be cached in the session's memory.
- Query
- Query_async
- RawQuery
- RawQuery_async
- RQL
using (var session = store.OpenSession())
{
// Query for product documents
var products = session.Query<Product>()
// Filter documents as needed
.Where(x => x.Supplier == "suppliers/1-A")
.Include(includeBuilder =>
// Include ALL counters for each matching document
includeBuilder.IncludeAllCounters())
.ToList();
foreach (var product in products)
{
// All counters for the resulting documents are now cached in the session's memory
// Getting their values will NOT trigger a server request
var productDislikes = session.CountersFor(product).Get("Dislikes");
}
}
using (var asyncSession = store.OpenAsyncSession())
{
// Query for product documents
var products = await asyncSession.Query<Product>()
// Filter documents as needed
.Where(x => x.Supplier == "suppliers/1-A")
.Include(includeBuilder =>
// Include ALL counters for each matching document
includeBuilder.IncludeAllCounters())
.ToListAsync();
foreach (var product in products)
{
// All counters for the resulting documents are now cached in the session's memory
// Getting their values will NOT trigger a server request
var productDislikes = await asyncSession.CountersFor(product).GetAsync("Dislikes");
}
}
using (var session = store.OpenSession())
{
var products = session.Advanced.RawQuery<Product>(@"
from Products
where Supplier == 'suppliers/1-A'
include counters()
")
.ToList();
foreach (var product in products)
{
var productDislikes = session.CountersFor(product).Get("Dislikes");
}
}
using (var asyncSession = store.OpenAsyncSession())
{
var products = await asyncSession.Advanced.AsyncRawQuery<Product>(@"
from Products
where Supplier == 'suppliers/1-A'
include counters()
")
.ToListAsync();
foreach (var product in products)
{
var productDislikes = await asyncSession.CountersFor(product).GetAsync("Dislikes");
}
}
from "Products"
where Supplier == "suppliers/1-A"
include counters()
Including multiple item types
You can combine different include types when loading or querying a document using the fluent includeBuilder syntax.
For example, you can include counters, related documents, time series, compare-exchange values, or past revisions.
This allows you to retrieve all related data in a single server call and avoid additional round trips later in the session.
The order in which you chain the include methods does not matter. For example:
using (var session = store.OpenSession(new SessionOptions
{
// Required when including compare-exchange values
TransactionMode = TransactionMode.ClusterWide
}))
{
var product = session.Load<Product>("products/1-A", includeBuilder =>
includeBuilder
// include counter
.IncludeCounter("Likes")
// include related document
.IncludeDocuments(x => x.Supplier)
// include time series
.IncludeTimeSeries("HeartRates")
// include compare-exchange value
.IncludeCompareExchangeValue("document_property_that_holds_the_cmpxchg_Key")
// include past revisions from last month
.IncludeRevisions(DateTime.Now.AddMonths(-1))
);
}
Include behavior and constraints
The following sections describe behavior that applies when including counters - whether during load or query:
- Counters that don't exist at include time
- Do not mix IncludeAllCounters with individual counter includes
Counters that don't exist at include time
If you include a counter that does Not exist at the time of the load or query execution, this will not cause an error.
However:
- Its value will be null.
- Accessing it later in the same session will not trigger a server request, because the counter is already cached in the session’s internal state.
- Even if the counter is created after the include, no server call will be made when trying to access its value.
- To retrieve the counter in this case, you can:
using (var session = store.OpenSession())
{
// Load and include specific counters - one of them doesn't exist yet
var product = session.Load<Product>("products/1-A", includeBuilder =>
includeBuilder.IncludeCounters(new[] { "Likes", "non-existent-counter" }));
// ...ASSUME the counter "non-existent-counter" was just created *After* the above load
// Trying to get the counter's value will NOT trigger a server call.
// The counter is already cached in the session's internal state.
var valueOfCounter = session.CountersFor(product).Get("non-existent-counter");
Console.WriteLine("Value of counter: " + valueOfCounter); // null
// You can call 'Evict' to remove the document (and its cached counters) from the session
session.Advanced.Evict(product);
// Now a server call will be made to fetch the counter.
// Note:
// * You must specify the document ID since the entity is no longer tracked
// * Despite the name, "non-existent-counter" now exists on the server :)
valueOfCounter = session.CountersFor("products/1-A").Get("non-existent-counter");
Console.WriteLine("Value of counter: " + valueOfCounter); // whatever the current value is
}
using (var session = store.OpenSession())
{
// Load and include ALL counters
var product = session.Load<Product>("products/1-A", includeBuilder =>
includeBuilder.IncludeAllCounters());
// ...ASSUME the counter "new-counter" was just created *After* the above load
// No server call is made here, even if "new-counter" exists now
var valueOfCounter = session.CountersFor(product).Get("new-counter");
Console.WriteLine("Value of counter: " + valueOfCounter); // null
}
Do not mix IncludeAllCounters with individual counter includes
Once you've called IncludeAllCounters(), you cannot include individual counters using IncludeCounter() or IncludeCounters() in the same include chain.
Attempting to do so will throw an InvalidOperationException at runtime.
using (var session = store.OpenSession())
{
session.Load<Product>("products/1-A", includeBuilder => includeBuilder
.IncludeAllCounters()
.IncludeCounter("Likes")); // This will throw
}
Syntax
// Available overloads:
IncludeCounter(string name);
IncludeCounters(string[] names);
IncludeAllCounters();
| Parameter | Type | Description |
|---|---|---|
| name | string | The name of a single counter to include. |
| names | string[] | An array of counter names to include. |