diff --git a/src/Client/Core/DependencyInjection/DefaultDurableTaskClientBuilder.cs b/src/Client/Core/DependencyInjection/DefaultDurableTaskClientBuilder.cs
index c08b2003..10316c80 100644
--- a/src/Client/Core/DependencyInjection/DefaultDurableTaskClientBuilder.cs
+++ b/src/Client/Core/DependencyInjection/DefaultDurableTaskClientBuilder.cs
@@ -9,26 +9,20 @@ namespace Microsoft.DurableTask.Client;
///
/// Default builder for .
///
-public class DefaultDurableTaskClientBuilder : IDurableTaskClientBuilder
+///
+/// Initializes a new instance of the class.
+///
+/// The name of the builder.
+/// The service collection.
+public class DefaultDurableTaskClientBuilder(string? name, IServiceCollection services) : IDurableTaskClientBuilder
{
Type? buildTarget;
- ///
- /// Initializes a new instance of the class.
- ///
- /// The name of the builder.
- /// The service collection.
- public DefaultDurableTaskClientBuilder(string? name, IServiceCollection services)
- {
- this.Name = name ?? Options.DefaultName;
- this.Services = services;
- }
-
///
- public string Name { get; }
+ public string Name { get; } = name ?? Options.DefaultName;
///
- public IServiceCollection Services { get; }
+ public IServiceCollection Services { get; } = services;
///
public Type? BuildTarget
diff --git a/src/Client/Core/DurableTaskClientOptions.cs b/src/Client/Core/DurableTaskClientOptions.cs
index 142be7e5..296b750d 100644
--- a/src/Client/Core/DurableTaskClientOptions.cs
+++ b/src/Client/Core/DurableTaskClientOptions.cs
@@ -11,6 +11,7 @@ namespace Microsoft.DurableTask.Client;
public class DurableTaskClientOptions
{
DataConverter dataConverter = JsonDataConverter.Default;
+ bool enableEntitySupport;
///
/// Gets or sets the data converter. Default value is .
@@ -50,7 +51,15 @@ public DataConverter DataConverter
/// Gets or sets a value indicating whether this client should support entities. If true, all instance ids starting with '@' are reserved for entities,
/// and validation checks are performed where appropriate.
///
- public bool EnableEntitySupport { get; set; }
+ public bool EnableEntitySupport
+ {
+ get => this.enableEntitySupport;
+ set
+ {
+ this.enableEntitySupport = value;
+ this.EntitySupportExplicitlySet = true;
+ }
+ }
///
/// Gets a value indicating whether was explicitly set or not.
@@ -63,6 +72,11 @@ public DataConverter DataConverter
///
internal bool DataConverterExplicitlySet { get; private set; }
+ ///
+ /// Gets a value indicating whether was explicitly set or not.
+ ///
+ internal bool EntitySupportExplicitlySet { get; private set; }
+
///
/// Applies these option values to another.
///
@@ -72,8 +86,15 @@ internal void ApplyTo(DurableTaskClientOptions other)
if (other is not null)
{
// Make sure to keep this up to date as values are added.
- other.DataConverter = this.DataConverter;
- other.EnableEntitySupport = this.EnableEntitySupport;
+ if (!other.DataConverterExplicitlySet)
+ {
+ other.DataConverter = this.DataConverter;
+ }
+
+ if (!other.EntitySupportExplicitlySet)
+ {
+ other.EnableEntitySupport = this.EnableEntitySupport;
+ }
}
}
}
diff --git a/src/Client/Core/Entities/EntityMetadata.cs b/src/Client/Core/Entities/EntityMetadata.cs
index 9eaf5671..f0064853 100644
--- a/src/Client/Core/Entities/EntityMetadata.cs
+++ b/src/Client/Core/Entities/EntityMetadata.cs
@@ -11,21 +11,15 @@ namespace Microsoft.DurableTask.Client.Entities;
/// Represents entity metadata.
///
/// The type of state held by the metadata.
+///
+/// Initializes a new instance of the class.
+///
+/// The ID of the entity.
[JsonConverter(typeof(EntityMetadataConverter))]
-public class EntityMetadata
+public class EntityMetadata(EntityInstanceId id)
{
readonly TState? state;
- ///
- /// Initializes a new instance of the class.
- ///
- /// The ID of the entity.
- public EntityMetadata(EntityInstanceId id)
- {
- this.Id = Check.NotDefault(id);
- this.IncludesState = false;
- }
-
///
/// Initializes a new instance of the class.
///
@@ -41,7 +35,7 @@ public EntityMetadata(EntityInstanceId id, TState? state)
///
/// Gets the ID for this entity.
///
- public EntityInstanceId Id { get; }
+ public EntityInstanceId Id { get; } = Check.NotDefault(id);
///
/// Gets the time the entity was last modified.
@@ -64,9 +58,9 @@ public EntityMetadata(EntityInstanceId id, TState? state)
///
/// Queries can exclude the state of the entity from the metadata that is retrieved.
///
- [MemberNotNullWhen(true, "State")]
- [MemberNotNullWhen(true, "state")]
- public bool IncludesState { get; }
+ [MemberNotNullWhen(true, nameof(State))]
+ [MemberNotNullWhen(true, nameof(state))]
+ public bool IncludesState { get; } = false;
///
/// Gets the state for this entity.
@@ -96,16 +90,13 @@ public TState State
///
/// Represents the metadata for a durable entity instance.
///
+///
+/// Initializes a new instance of the class.
+///
+/// The ID of the entity.
+/// The state of this entity.
[JsonConverter(typeof(EntityMetadataConverter))]
-public sealed class EntityMetadata : EntityMetadata
+public sealed class EntityMetadata(EntityInstanceId id, SerializedData? state = null)
+ : EntityMetadata(id, state)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The ID of the entity.
- /// The state of this entity.
- public EntityMetadata(EntityInstanceId id, SerializedData? state = null)
- : base(id, state)
- {
- }
}
diff --git a/src/Client/Core/SerializedData.cs b/src/Client/Core/SerializedData.cs
index 6a777680..7bd2c127 100644
--- a/src/Client/Core/SerializedData.cs
+++ b/src/Client/Core/SerializedData.cs
@@ -8,28 +8,22 @@ namespace Microsoft.DurableTask.Client;
///
/// Gets a type representing serialized data.
///
-public sealed class SerializedData
+///
+/// Initializes a new instance of the class.
+///
+/// The serialized data.
+/// The data converter.
+public sealed class SerializedData(string data, DataConverter? converter = null)
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The serialized data.
- /// The data converter.
- public SerializedData(string data, DataConverter? converter = null)
- {
- this.Value = Check.NotNull(data);
- this.Converter = converter ?? JsonDataConverter.Default;
- }
-
///
/// Gets the serialized value.
///
- public string Value { get; }
+ public string Value { get; } = Check.NotNull(data);
///
/// Gets the data converter.
///
- public DataConverter Converter { get; }
+ public DataConverter Converter { get; } = converter ?? JsonDataConverter.Default;
///
/// Deserializes the data into .
diff --git a/src/Client/OrchestrationServiceClientShim/Client.OrchestrationServiceClientShim.csproj b/src/Client/OrchestrationServiceClientShim/Client.OrchestrationServiceClientShim.csproj
index b5389866..aa687a11 100644
--- a/src/Client/OrchestrationServiceClientShim/Client.OrchestrationServiceClientShim.csproj
+++ b/src/Client/OrchestrationServiceClientShim/Client.OrchestrationServiceClientShim.csproj
@@ -8,13 +8,6 @@ The client is responsible for interacting with orchestrations from outside the w
true
-
-
- 1.0.5
-
-
-
-
diff --git a/src/Client/OrchestrationServiceClientShim/DependencyInjection/DurableTaskClientBuilderExtensions.cs b/src/Client/OrchestrationServiceClientShim/DependencyInjection/DurableTaskClientBuilderExtensions.cs
index ba69a58e..7d2cf12b 100644
--- a/src/Client/OrchestrationServiceClientShim/DependencyInjection/DurableTaskClientBuilderExtensions.cs
+++ b/src/Client/OrchestrationServiceClientShim/DependencyInjection/DurableTaskClientBuilderExtensions.cs
@@ -2,8 +2,11 @@
// Licensed under the MIT License.
using DurableTask.Core;
+using DurableTask.Core.Entities;
using Microsoft.DurableTask.Client.OrchestrationServiceClientShim;
using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.DependencyInjection.Extensions;
+using Microsoft.Extensions.Options;
namespace Microsoft.DurableTask.Client;
@@ -54,21 +57,74 @@ public static IDurableTaskClientBuilder UseOrchestrationService(
{
Check.NotNull(builder);
Check.NotNull(configure);
- builder.Services.Configure(builder.Name, configure);
- builder.Services.AddOptions(builder.Name)
- .PostConfigure((opt, sp) =>
+ builder.Services.AddOptions(builder.Name).Configure(configure);
+ builder.Services.TryAddSingleton, OptionsConfigure>();
+ builder.Services.TryAddSingleton, OptionsValidator>();
+ return builder.UseBuildTarget();
+ }
+
+ static IEntityOrchestrationService? GetEntityService(
+ IServiceProvider services, ShimDurableTaskClientOptions options)
+ {
+ return options.Client as IEntityOrchestrationService
+ ?? services.GetService()
+ ?? services.GetService() as IEntityOrchestrationService
+ ?? services.GetService() as IEntityOrchestrationService;
+ }
+
+ class OptionsConfigure(IServiceProvider services) : IPostConfigureOptions
+ {
+ public void PostConfigure(string name, ShimDurableTaskClientOptions options)
+ {
+ ConfigureClient(services, options);
+ ConfigureEntities(name, services, options); // Must be called after ConfigureClient.
+ }
+
+ static void ConfigureClient(IServiceProvider services, ShimDurableTaskClientOptions options)
+ {
+ if (options.Client is not null)
{
- if (opt.Client is not null)
- {
- return;
- }
+ return;
+ }
- // Try to resolve client from service container.
- opt.Client = sp.GetService()
- ?? sp.GetService() as IOrchestrationServiceClient;
- })
- .Validate(x => x.Client is not null, "ShimDurableTaskClientOptions.Client must not be null.");
+ // Try to resolve client from service container.
+ options.Client = services.GetService()
+ ?? services.GetService() as IOrchestrationServiceClient;
+ }
- return builder.UseBuildTarget();
+ static void ConfigureEntities(string name, IServiceProvider services, ShimDurableTaskClientOptions options)
+ {
+ if (options.Entities.Queries is null)
+ {
+ options.Entities.Queries = services.GetService()
+ ?? GetEntityService(services, options)?.EntityBackendQueries;
+ }
+
+ if (options.Entities.MaxSignalDelayTime is null)
+ {
+ EntityBackendProperties? properties = services.GetService>()?.Get(name)
+ ?? GetEntityService(services, options)?.EntityBackendProperties;
+ options.Entities.MaxSignalDelayTime = properties?.MaximumSignalDelayTime;
+ }
+ }
+ }
+
+ class OptionsValidator : IValidateOptions
+ {
+ public ValidateOptionsResult Validate(string name, ShimDurableTaskClientOptions options)
+ {
+ if (options.Client is null)
+ {
+ return ValidateOptionsResult.Fail("ShimDurableTaskClientOptions.Client must not be null.");
+ }
+
+ if (options.EnableEntitySupport && options.Entities.Queries is null)
+ {
+ return ValidateOptionsResult.Fail(
+ "ShimDurableTaskClientOptions.Entities.Queries must not be null when entity support is enabled.");
+ }
+
+ return ValidateOptionsResult.Success;
+ }
}
}
diff --git a/src/Client/OrchestrationServiceClientShim/ShimDurableEntityClient.cs b/src/Client/OrchestrationServiceClientShim/ShimDurableEntityClient.cs
new file mode 100644
index 00000000..10571e37
--- /dev/null
+++ b/src/Client/OrchestrationServiceClientShim/ShimDurableEntityClient.cs
@@ -0,0 +1,172 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using DurableTask.Core;
+using DurableTask.Core.Entities;
+using Microsoft.DurableTask.Client.Entities;
+using Microsoft.DurableTask.Entities;
+
+namespace Microsoft.DurableTask.Client.OrchestrationServiceClientShim;
+
+///
+/// A shim client for interacting with entities backend via .
+///
+///
+/// Initializes a new instance of the class.
+///
+/// The name of this client.
+/// The client options..
+class ShimDurableEntityClient(string name, ShimDurableTaskClientOptions options) : DurableEntityClient(name)
+{
+ readonly ShimDurableTaskClientOptions options = Check.NotNull(options);
+
+ EntityBackendQueries Queries => this.options.Entities.Queries!;
+
+ DataConverter Converter => this.options.DataConverter;
+
+ ///
+ public override async Task CleanEntityStorageAsync(
+ CleanEntityStorageRequest? request = null,
+ bool continueUntilComplete = true,
+ CancellationToken cancellation = default)
+ {
+ CleanEntityStorageRequest r = request ?? CleanEntityStorageRequest.Default;
+ EntityBackendQueries.CleanEntityStorageResult result = await this.Queries.CleanEntityStorageAsync(
+ new EntityBackendQueries.CleanEntityStorageRequest()
+ {
+ RemoveEmptyEntities = r.RemoveEmptyEntities,
+ ReleaseOrphanedLocks = r.ReleaseOrphanedLocks,
+ ContinuationToken = r.ContinuationToken,
+ },
+ cancellation);
+
+ return new()
+ {
+ EmptyEntitiesRemoved = result.EmptyEntitiesRemoved,
+ OrphanedLocksReleased = result.OrphanedLocksReleased,
+ ContinuationToken = result.ContinuationToken,
+ };
+ }
+
+ ///
+ public override AsyncPageable GetAllEntitiesAsync(EntityQuery? filter = null)
+ => this.GetAllEntitiesAsync(this.Convert, filter);
+
+ ///
+ public override AsyncPageable> GetAllEntitiesAsync(EntityQuery? filter = null)
+ => this.GetAllEntitiesAsync(this.Convert, filter);
+
+ ///
+ public override async Task GetEntityAsync(
+ EntityInstanceId id, bool includeState = true, CancellationToken cancellation = default)
+ => this.Convert(await this.Queries.GetEntityAsync(
+ id.ConvertToCore(), includeState, false, cancellation));
+
+ ///
+ public override async Task?> GetEntityAsync(
+ EntityInstanceId id, bool includeState = true, CancellationToken cancellation = default)
+ => this.Convert(await this.Queries.GetEntityAsync(
+ id.ConvertToCore(), includeState, false, cancellation));
+
+ ///
+ public override async Task SignalEntityAsync(
+ EntityInstanceId id,
+ string operationName,
+ object? input = null,
+ SignalEntityOptions? options = null,
+ CancellationToken cancellation = default)
+ {
+ Check.NotNullOrEmpty(id.Name);
+ Check.NotNull(id.Key);
+
+ DateTimeOffset? scheduledTime = options?.SignalTime;
+ string? serializedInput = this.Converter.Serialize(input);
+
+ EntityMessageEvent eventToSend = ClientEntityHelpers.EmitOperationSignal(
+ new OrchestrationInstance() { InstanceId = id.ToString() },
+ Guid.NewGuid(),
+ operationName,
+ serializedInput,
+ EntityMessageEvent.GetCappedScheduledTime(
+ DateTime.UtcNow,
+ this.options.Entities.MaxSignalDelayTimeOrDefault,
+ scheduledTime?.UtcDateTime));
+
+ await this.options.Client!.SendTaskOrchestrationMessageAsync(eventToSend.AsTaskMessage());
+ }
+
+ AsyncPageable GetAllEntitiesAsync(
+ Func select,
+ EntityQuery? filter)
+ where TMetadata : notnull
+ {
+ bool includeState = filter?.IncludeState ?? true;
+ bool includeTransient = filter?.IncludeTransient ?? false;
+ string startsWith = filter?.InstanceIdStartsWith ?? string.Empty;
+ DateTime? lastModifiedFrom = filter?.LastModifiedFrom?.UtcDateTime;
+ DateTime? lastModifiedTo = filter?.LastModifiedTo?.UtcDateTime;
+
+ return Pageable.Create(async (continuation, size, cancellation) =>
+ {
+ continuation ??= filter?.ContinuationToken;
+ size ??= filter?.PageSize;
+ EntityBackendQueries.EntityQueryResult result = await this.Queries.QueryEntitiesAsync(
+ new EntityBackendQueries.EntityQuery()
+ {
+ InstanceIdStartsWith = startsWith,
+ LastModifiedFrom = lastModifiedFrom,
+ LastModifiedTo = lastModifiedTo,
+ IncludeTransient = includeTransient,
+ IncludeState = includeState,
+ ContinuationToken = continuation,
+ PageSize = size,
+ },
+ cancellation);
+
+ return new Page([.. result.Results.Select(select)], result.ContinuationToken);
+ });
+ }
+
+ EntityMetadata Convert(EntityBackendQueries.EntityMetadata metadata)
+ {
+ return new(
+ metadata.EntityId.ConvertFromCore(),
+ this.Converter.Deserialize(metadata.SerializedState))
+ {
+ LastModifiedTime = metadata.LastModifiedTime,
+ BacklogQueueSize = metadata.BacklogQueueSize,
+ LockedBy = metadata.LockedBy,
+ };
+ }
+
+ EntityMetadata? Convert(EntityBackendQueries.EntityMetadata? metadata)
+ {
+ if (metadata is null)
+ {
+ return null;
+ }
+
+ return this.Convert(metadata.Value);
+ }
+
+ EntityMetadata Convert(EntityBackendQueries.EntityMetadata metadata)
+ {
+ SerializedData? data = metadata.SerializedState is null ? null : new(metadata.SerializedState, this.Converter);
+ return new(new EntityInstanceId(metadata.EntityId.Name, metadata.EntityId.Key), data)
+ {
+ LastModifiedTime = metadata.LastModifiedTime,
+ BacklogQueueSize = metadata.BacklogQueueSize,
+ LockedBy = metadata.LockedBy,
+ };
+ }
+
+ EntityMetadata? Convert(EntityBackendQueries.EntityMetadata? metadata)
+ {
+ if (metadata is null)
+ {
+ return null;
+ }
+
+ return this.Convert(metadata.Value);
+ }
+}
diff --git a/src/Client/OrchestrationServiceClientShim/ShimDurableTaskClient.cs b/src/Client/OrchestrationServiceClientShim/ShimDurableTaskClient.cs
index 3f3c5211..ce510fd0 100644
--- a/src/Client/OrchestrationServiceClientShim/ShimDurableTaskClient.cs
+++ b/src/Client/OrchestrationServiceClientShim/ShimDurableTaskClient.cs
@@ -3,8 +3,10 @@
using System.Diagnostics.CodeAnalysis;
using DurableTask.Core;
+using DurableTask.Core.Entities;
using DurableTask.Core.History;
using DurableTask.Core.Query;
+using Microsoft.DurableTask.Client.Entities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Core = DurableTask.Core;
@@ -15,9 +17,15 @@ namespace Microsoft.DurableTask.Client.OrchestrationServiceClientShim;
///
/// A shim client for interacting with the backend via .
///
-class ShimDurableTaskClient : DurableTaskClient
+///
+/// Initializes a new instance of the class.
+///
+/// The name of the client.
+/// The client options.
+class ShimDurableTaskClient(string name, ShimDurableTaskClientOptions options) : DurableTaskClient(name)
{
- readonly ShimDurableTaskClientOptions options;
+ readonly ShimDurableTaskClientOptions options = Check.NotNull(options);
+ ShimDurableEntityClient? entities;
///
/// Initializes a new instance of the class.
@@ -31,15 +39,29 @@ public ShimDurableTaskClient(
{
}
- ///
- /// Initializes a new instance of the class.
- ///
- /// The name of the client.
- /// The client options.
- public ShimDurableTaskClient(string name, ShimDurableTaskClientOptions options)
- : base(name)
+ ///
+ public override DurableEntityClient Entities
{
- this.options = Check.NotNull(options);
+ get
+ {
+ if (!this.options.EnableEntitySupport)
+ {
+ throw new InvalidOperationException("Entity support is not enabled.");
+ }
+
+ if (this.entities is null)
+ {
+ if (this.options.Entities.Queries is null)
+ {
+ throw new NotSupportedException(
+ "The configured IOrchestrationServiceClient does not support entities.");
+ }
+
+ this.entities = new(this.Name, this.options);
+ }
+
+ return this.entities;
+ }
}
DataConverter DataConverter => this.options.DataConverter;
diff --git a/src/Client/OrchestrationServiceClientShim/ShimDurableTaskClientOptions.cs b/src/Client/OrchestrationServiceClientShim/ShimDurableTaskClientOptions.cs
index 361694b3..8d535483 100644
--- a/src/Client/OrchestrationServiceClientShim/ShimDurableTaskClientOptions.cs
+++ b/src/Client/OrchestrationServiceClientShim/ShimDurableTaskClientOptions.cs
@@ -2,6 +2,8 @@
// Licensed under the MIT License.
using DurableTask.Core;
+using DurableTask.Core.Entities;
+using Microsoft.DurableTask.Client.Entities;
namespace Microsoft.DurableTask.Client.OrchestrationServiceClientShim;
@@ -15,4 +17,33 @@ public sealed class ShimDurableTaskClientOptions : DurableTaskClientOptions
/// If not manually set, this will be resolved from the , if available.
///
public IOrchestrationServiceClient? Client { get; set; }
+
+ ///
+ /// Gets the to configure entity support.
+ ///
+ public ShimDurableTaskEntityOptions Entities { get; } = new();
+}
+
+///
+/// Options for entities.
+///
+public class ShimDurableTaskEntityOptions
+{
+ ///
+ /// Gets or sets the to use in the .
+ /// If not set manually, this will attempt to be resolved automatically by looking for
+ /// in the .
+ ///
+ public EntityBackendQueries? Queries { get; set; }
+
+ ///
+ /// Gets or sets the maximum time span to use for signal delay. If not set manually, will attempt to be resolved
+ /// through the service container. This will finally default to 3 days if it cannot be any other means.
+ ///
+ public TimeSpan? MaxSignalDelayTime { get; set; }
+
+ ///
+ /// Gets the max signal delay time.
+ ///
+ internal TimeSpan MaxSignalDelayTimeOrDefault => this.MaxSignalDelayTime ?? TimeSpan.FromDays(3);
}
diff --git a/src/Client/OrchestrationServiceClientShim/ShimExtensions.cs b/src/Client/OrchestrationServiceClientShim/ShimExtensions.cs
index 5ab3b96e..50191e37 100644
--- a/src/Client/OrchestrationServiceClientShim/ShimExtensions.cs
+++ b/src/Client/OrchestrationServiceClientShim/ShimExtensions.cs
@@ -2,6 +2,8 @@
// Licensed under the MIT License.
using System.Diagnostics.CodeAnalysis;
+using DurableTask.Core.Entities;
+using Microsoft.DurableTask.Entities;
using Core = DurableTask.Core;
namespace Microsoft.DurableTask.Client;
@@ -109,4 +111,20 @@ public static Core.OrchestrationStatus ConvertToCore(this OrchestrationRuntimeSt
return new Core.PurgeInstanceFilter(
(filter.CreatedFrom ?? default).UtcDateTime, filter.CreatedTo?.UtcDateTime, statuses);
}
+
+ ///
+ /// Convert to .
+ ///
+ /// The entity ID to convert.
+ /// The converted entity instance ID.
+ public static EntityInstanceId ConvertFromCore(this EntityId entityId)
+ => new(entityId.Name, entityId.Key);
+
+ ///
+ /// Convert to .
+ ///
+ /// The entity instance ID to convert.
+ /// The converted entity ID.
+ public static EntityId ConvertToCore(this EntityInstanceId entityId)
+ => new(entityId.Name, entityId.Key);
}
diff --git a/test/Client/OrchestrationServiceClientShim.Tests/DependencyInjection/DurableTaskClientBuilderExtensionsTests.cs b/test/Client/OrchestrationServiceClientShim.Tests/DependencyInjection/DurableTaskClientBuilderExtensionsTests.cs
index ccbaeaba..37db1647 100644
--- a/test/Client/OrchestrationServiceClientShim.Tests/DependencyInjection/DurableTaskClientBuilderExtensionsTests.cs
+++ b/test/Client/OrchestrationServiceClientShim.Tests/DependencyInjection/DurableTaskClientBuilderExtensionsTests.cs
@@ -2,6 +2,7 @@
// Licensed under the MIT License.
using DurableTask.Core;
+using DurableTask.Core.Entities;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
@@ -85,4 +86,139 @@ public void UseOrchestrationService_Callback_Sets()
options.Client.Should().Be(client);
}
+
+ [Fact]
+ public void EnableEntities_NoBackendSupport_Throws()
+ {
+ ServiceCollection services = new();
+ IOrchestrationServiceClient client = Mock.Of();
+ services.AddSingleton(client);
+ DefaultDurableTaskClientBuilder builder = new(null, services);
+
+ builder.UseOrchestrationService(o => o.EnableEntitySupport = true);
+
+ IServiceProvider provider = services.BuildServiceProvider();
+ Action act = () => provider.GetOptions();
+ act.Should().ThrowExactly()
+ .WithMessage("ShimDurableTaskClientOptions.Entities.Queries must not be null when entity support is enabled.");
+ }
+
+ [Fact]
+ public void EnableEntities_BackendSupport_ExplicitProvider()
+ {
+ ServiceCollection services = new();
+ IOrchestrationServiceClient client = Mock.Of();
+ services.AddSingleton(client);
+ Mock mock = new();
+ DefaultDurableTaskClientBuilder builder = new(null, services);
+
+ builder.UseOrchestrationService(o =>
+ {
+ o.EnableEntitySupport = true;
+ o.Entities.Queries = mock.Object;
+ });
+
+ IServiceProvider provider = services.BuildServiceProvider();
+ provider.GetOptions(); // no-throw
+ }
+
+ [Fact]
+ public void EnableEntities_BackendSupport_RegisteredService1()
+ {
+ ServiceCollection services = new();
+ IOrchestrationServiceClient client = Mock.Of();
+ services.AddSingleton(client);
+ EntityBackendQueries queries = Mock.Of();
+ services.AddSingleton(queries);
+ DefaultDurableTaskClientBuilder builder = new(null, services);
+
+ builder.UseOrchestrationService(o => o.EnableEntitySupport = true );
+
+ IServiceProvider provider = services.BuildServiceProvider();
+ ShimDurableTaskClientOptions options = provider.GetOptions();
+ options.Entities.Queries.Should().Be(queries);
+ }
+
+ [Fact]
+ public void EnableEntities_BackendSupport_RegisteredService2()
+ {
+ ServiceCollection services = new();
+ Mock client = new();
+ EntityBackendQueries queries = Mock.Of();
+ Mock entities = client.As();
+ entities.Setup(m => m.EntityBackendQueries).Returns(queries);
+ services.AddSingleton(client.Object);
+
+ DefaultDurableTaskClientBuilder builder = new(null, services);
+
+ builder.UseOrchestrationService(o => o.EnableEntitySupport = true);
+
+ IServiceProvider provider = services.BuildServiceProvider();
+ ShimDurableTaskClientOptions options = provider.GetOptions();
+ options.Entities.Queries.Should().Be(queries);
+ }
+
+ [Fact]
+ public void EnableEntities_BackendSupport_RegisteredService3()
+ {
+ ServiceCollection services = new();
+ Mock client = new();
+ EntityBackendQueries queries = Mock.Of();
+ Mock entities = client.As();
+ entities.Setup(m => m.EntityBackendQueries).Returns(queries);
+
+ DefaultDurableTaskClientBuilder builder = new(null, services);
+
+ builder.UseOrchestrationService(o =>
+ {
+ o.Client = client.Object;
+ o.EnableEntitySupport = true;
+ });
+
+ IServiceProvider provider = services.BuildServiceProvider();
+ ShimDurableTaskClientOptions options = provider.GetOptions();
+ options.Entities.Queries.Should().Be(queries);
+ }
+
+ [Fact]
+ public void EnableEntities_BackendSupport_RegisteredService4()
+ {
+ ServiceCollection services = new();
+ IOrchestrationServiceClient client = Mock.Of();
+ services.AddSingleton(client);
+
+ EntityBackendQueries queries = Mock.Of();
+ IEntityOrchestrationService entities = Mock.Of(m => m.EntityBackendQueries == queries);
+ services.AddSingleton(entities);
+
+ DefaultDurableTaskClientBuilder builder = new(null, services);
+
+ builder.UseOrchestrationService(o => o.EnableEntitySupport = true);
+
+ IServiceProvider provider = services.BuildServiceProvider();
+ ShimDurableTaskClientOptions options = provider.GetOptions();
+ options.Entities.Queries.Should().Be(queries);
+ }
+
+ [Fact]
+ public void EnableEntities_BackendSupport_RegisteredService5()
+ {
+ ServiceCollection services = new();
+ IOrchestrationServiceClient client = Mock.Of();
+ services.AddSingleton(client);
+
+ EntityBackendQueries queries = Mock.Of();
+ Mock orchestration = new();
+ Mock entities = orchestration.As();
+ entities.Setup(m => m.EntityBackendQueries).Returns(queries);
+ services.AddSingleton(orchestration.Object);
+
+ DefaultDurableTaskClientBuilder builder = new(null, services);
+
+ builder.UseOrchestrationService(o => o.EnableEntitySupport = true);
+
+ IServiceProvider provider = services.BuildServiceProvider();
+ ShimDurableTaskClientOptions options = provider.GetOptions();
+ options.Entities.Queries.Should().Be(queries);
+ }
}
diff --git a/test/Client/OrchestrationServiceClientShim.Tests/ShimDurableEntityClientTests.cs b/test/Client/OrchestrationServiceClientShim.Tests/ShimDurableEntityClientTests.cs
new file mode 100644
index 00000000..b3c4ac06
--- /dev/null
+++ b/test/Client/OrchestrationServiceClientShim.Tests/ShimDurableEntityClientTests.cs
@@ -0,0 +1,364 @@
+// Copyright (c) Microsoft Corporation.
+// Licensed under the MIT License.
+
+using System.Linq.Expressions;
+using DotNext;
+using DurableTask.Core;
+using DurableTask.Core.Entities;
+using DurableTask.Core.History;
+using FluentAssertions.Execution;
+using Microsoft.DurableTask.Client.Entities;
+using Microsoft.DurableTask.Converters;
+using Microsoft.DurableTask.Entities;
+
+namespace Microsoft.DurableTask.Client.OrchestrationServiceClientShim.Tests;
+
+public class ShimDurableEntityClientTests
+{
+ static readonly DataConverter Converter = new JsonDataConverter();
+ readonly Mock query = new(MockBehavior.Strict);
+ readonly Mock client = new(MockBehavior.Strict);
+
+ [Fact]
+ public void Ctor_NullOptions_Throws()
+ {
+ Func