Skip to main content
Including Counters

Including Counters

Sample data

The examples in this article are based on the following sample data:

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();
}

Include counters when loading

Include single counter:

Include a single counter using IncludeCounter():

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);
}

Include multiple counters:

Include multiple counters using IncludeCounters():

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);
}

You can also include multiple counters by chaining multiple IncludeCounter() calls:

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);
}

Include all counters:

Include ALL counters using IncludeAllCounters():

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);
}

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.

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.");
}
}

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.

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");
}
}

Include all counters:

Use IncludeAllCounters() to include ALL counters.
All counters for each resulting document will be cached in the session's memory.

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");
}
}

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

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:
    • Call Clear to reset the session and discard all tracked state.
    • Use Evict to remove the document (and its cached counters) from the session’s internal state.
    • Or simply fetch the counter in a new session.
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();

ParameterTypeDescription
namestringThe name of a single counter to include.
namesstring[]An array of counter names to include.