Skip to content

Grain references

Before calling a method on a grain, you first need a reference to that grain. A grain reference is a proxy object implementing the same grain interface as the corresponding grain class. It encapsulates the logical identity (type and unique key) of the target grain. You use grain references to make calls to the target grain. Each grain reference points to a single grain (a single instance of the grain class), but you can create multiple independent references to the same grain.

Since a grain reference represents the logical identity of the target grain, it’s independent of the grain’s physical location and remains valid even after a complete system restart. You can use grain references like any other .NET object. You can pass it to a method, use it as a method return value, and even save it to persistent storage.

You can obtain a grain reference by passing the identity of a grain to the [IGrainFactory.GetGrain<T>](https://learn.microsoft.com/dotnet/api/orleans.igrainfactory.getgrain%60-1) method, where Tis the grain interface andkey` is the unique key of the grain within its type.

The following examples show how to obtain a grain reference for the IPlayerGrain interface defined previously.

From within a grain class:

// This would typically be read from an HTTP request parameter or elsewhere.
Guid playerId = Guid.NewGuid();
IPlayerGrain player = GrainFactory.GetGrain<IPlayerGrain>(playerId);

From Orleans client code:

// This would typically be read from an HTTP request parameter or elsewhere.
Guid playerId = Guid.NewGuid();
IPlayerGrain player = client.GetGrain<IPlayerGrain>(playerId);

Grain references contain three pieces of information:

  1. The grain type, which uniquely identifies the grain class.
  2. The grain key, which uniquely identifies a logical instance of that grain class.
  3. The interface which the grain reference must implement.

Notice that the preceding calls to IGrainFactory.GetGrain accepted only two of these three things:

  • The interface implemented by the grain reference, IPlayerGrain.
  • The grain key, which is the value of playerId.

Despite stating that a grain reference contains a grain type, key, and interface, the examples only provided Orleans with the key and interface. This is because Orleans maintains a mapping between grain interfaces and grain types. When you ask the grain factory for IShoppingCartGrain, Orleans consults its mapping to find the corresponding grain type so it can create the reference. This works when there’s only one implementation of a grain interface. However, if there are multiple implementations, you need to disambiguate them in the GetGrain call. For more information, see the next section, Disambiguating grain type resolution.

When multiple implementations of a grain interface exist, such as in the following example, Orleans attempts to determine the intended implementation when creating a grain reference. Consider the following example, where there are two implementations of the ICounterGrain interface:

public interface ICounterGrain : IGrainWithStringKey
{
ValueTask<int> UpdateCount();
}
public class UpCounterGrain : ICounterGrain
{
private int _count;
public ValueTask<string> UpdateCount() => new(++_count); // Increment count
}
public class DownCounterGrain : ICounterGrain
{
private int _count;
public ValueTask<string> UpdateCount() => new(--_count); // Decrement count
}

The following call to GetGrain throws an exception because Orleans doesn’t know how to unambiguously map ICounterGrain to one of the grain classes.

// This will throw an exception: there is no unambiguous mapping from ICounterGrain to a grain class.
ICounterGrain myCounter = grainFactory.GetGrain<ICounterGrain>("my-counter");

An System.ArgumentException is thrown with the following message:

Unable to identify a single appropriate grain type for interface ICounterGrain. Candidates: upcounter (UpCounterGrain), downcounter (DownCounterGrain)

The error message tells you which grain implementations Orleans found that match the requested grain interface type, ICounterGrain. It shows the grain type names (upcounter and downcounter) and the grain classes (UpCounterGrain and DownCounterGrain).

There are several ways to resolve this ambiguity, detailed in the following subsections.

Disambiguating grain types using unique marker interfaces

Section titled “Disambiguating grain types using unique marker interfaces”

The clearest way to disambiguate these grains is to give them unique grain interfaces. For example, if you add the interface IUpCounterGrain to the UpCounterGrain class and add the interface IDownCounterGrain to the DownCounterGrain class, as in the following example, you can resolve the correct grain reference by passing IUpCounterGrain or IDownCounterGrain to the GetGrain call instead of the ambiguous ICounterGrain type.

public interface ICounterGrain : IGrainWithStringKey
{
ValueTask<int> UpdateCount();
}
// Define unique interfaces for our implementations
public interface IUpCounterGrain : ICounterGrain, IGrainWithStringKey {}
public interface IDownCounterGrain : ICounterGrain, IGrainWithStringKey {}
public class UpCounterGrain : IUpCounterGrain
{
private int _count;
public ValueTask<string> UpdateCount() => new(++_count); // Increment count
}
public class DownCounterGrain : IDownCounterGrain
{
private int _count;
public ValueTask<string> UpdateCount() => new(--_count); // Decrement count
}

To create a reference to either grain, consider the following code:

// Get a reference to an UpCounterGrain.
ICounterGrain myUpCounter = grainFactory.GetGrain<IUpCounterGrain>("my-counter");
// Get a reference to a DownCounterGrain.
ICounterGrain myDownCounter = grainFactory.GetGrain<IDownCounterGrain>("my-counter");

Disambiguating grain types by providing a grain class prefix

Section titled “Disambiguating grain types by providing a grain class prefix”

You can provide a grain class name prefix to IGrainFactory.GetGrain, for example:

ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>("my-counter", grainClassNamePrefix: "Up");
ICounterGrain myDownCounter = grainFactory.GetGrain<ICounterGrain>("my-counter", grainClassNamePrefix: "Down");

Specifying the default grain implementation using the naming convention

Section titled “Specifying the default grain implementation using the naming convention”

When disambiguating multiple implementations of the same grain interface, Orleans selects an implementation using the convention of stripping a leading ‘I’ from the interface name. For example, if the interface name is ICounterGrain and there are two implementations, CounterGrain and DownCounterGrain, Orleans chooses CounterGrain when asked for a reference to ICounterGrain, as in the following example:

/// This will refer to an instance of CounterGrain, since that matches the convention.
ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>("my-counter");

Specifying the default grain type using an attribute

Section titled “Specifying the default grain type using an attribute”

You can add the Metadata.DefaultGrainTypeAttribute attribute to a grain interface to specify the grain type of the default implementation for that interface, as shown in the following example:

[DefaultGrainType("up-counter")]
public interface ICounterGrain : IGrainWithStringKey
{
ValueTask<int> UpdateCount();
}
[GrainType("up-counter")]
public class UpCounterGrain : ICounterGrain
{
private int _count;
public ValueTask<string> UpdateCount() => new(++_count); // Increment count
}
[GrainType("down-counter")]
public class DownCounterGrain : ICounterGrain
{
private int _count;
public ValueTask<string> UpdateCount() => new(--_count); // Decrement count
}
/// This will refer to an instance of UpCounterGrain, due to the [DefaultGrainType("up-counter"')] attribute
ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>("my-counter");

Disambiguating grain types by providing the resolved grain ID

Section titled “Disambiguating grain types by providing the resolved grain ID”

Some overloads of IGrainFactory.GetGrain accept an argument of type Runtime.GrainId. When using these overloads, Orleans doesn’t need to map from an interface type to a grain type, so there’s no ambiguity to resolve. For example:

public interface ICounterGrain : IGrainWithStringKey
{
ValueTask<int> UpdateCount();
}
[GrainType("up-counter")]
public class UpCounterGrain : ICounterGrain
{
private int _count;
public ValueTask<string> UpdateCount() => new(++_count); // Increment count
}
[GrainType("down-counter")]
public class DownCounterGrain : ICounterGrain
{
private int _count;
public ValueTask<string> UpdateCount() => new(--_count); // Decrement count
}
// This will refer to an instance of UpCounterGrain, since "up-counter" was specified as the grain type
// and the UpCounterGrain uses [GrainType("up-counter")] to specify its grain type.
ICounterGrain myUpCounter = grainFactory.GetGrain<ICounterGrain>(GrainId.Create("up-counter", "my-counter"));