Merge pull request #756 from craigfowler/feature/748-multi-tenant-EntityFramework
Resolve #748 multi tenant entity framework
This commit is contained in:
commit
994e55abe1
|
|
@ -9,19 +9,119 @@ namespace Elsa.Persistence.EntityFramework.Core.Extensions
|
|||
{
|
||||
public static class ServiceCollectionExtensions
|
||||
{
|
||||
public static ElsaOptions UseEntityFrameworkPersistence(this ElsaOptions elsa, Action<DbContextOptionsBuilder> configure, bool autoRunMigrations = true) =>
|
||||
/// <summary>
|
||||
/// Configures Elsa to use Entity Framework Core for persistence, using pooled DB Context instances.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Pooled DB Context instances is a performance optimisation which is documented in more detail at
|
||||
/// https://docs.microsoft.com/en-us/ef/core/performance/advanced-performance-topics?tabs=with-constant#dbcontext-pooling.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="elsa">An Elsa options builder</param>
|
||||
/// <param name="configure">A configuration builder callback</param>
|
||||
/// <param name="autoRunMigrations">If <c>true</c> then migration scripts will auto-run on application startup; if <c>false</c> then they will not</param>
|
||||
/// <returns>The Elsa options builder, so calls may be chained</returns>
|
||||
public static ElsaOptions UseEntityFrameworkPersistence(this ElsaOptions elsa,
|
||||
Action<DbContextOptionsBuilder> configure,
|
||||
bool autoRunMigrations = true) =>
|
||||
elsa.UseEntityFrameworkPersistence((_, builder) => configure(builder), autoRunMigrations);
|
||||
|
||||
public static ElsaOptions UseEntityFrameworkPersistence(this ElsaOptions elsa, Action<IServiceProvider, DbContextOptionsBuilder> configure, bool autorunMigrations = true)
|
||||
/// <summary>
|
||||
/// Configures Elsa to use Entity Framework Core for persistence, using pooled DB Context instances.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Pooled DB Context instances is a performance optimisation which is documented in more detail at
|
||||
/// https://docs.microsoft.com/en-us/ef/core/performance/advanced-performance-topics?tabs=with-constant#dbcontext-pooling.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="elsa">An Elsa options builder</param>
|
||||
/// <param name="configure">A configuration builder callback, which also provides access to a service provider</param>
|
||||
/// <param name="autoRunMigrations">If <c>true</c> then migration scripts will auto-run on application startup; if <c>false</c> then they will not</param>
|
||||
/// <returns>The Elsa options builder, so calls may be chained</returns>
|
||||
public static ElsaOptions UseEntityFrameworkPersistence(this ElsaOptions elsa,
|
||||
Action<IServiceProvider, DbContextOptionsBuilder> configure,
|
||||
bool autoRunMigrations = true) =>
|
||||
UseEntityFrameworkPersistence(elsa, configure, autoRunMigrations, true, ServiceLifetime.Singleton);
|
||||
|
||||
/// <summary>
|
||||
/// Configures Elsa to use Entity Framework Core for persistence, without using pooled DB Context instances.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Use this method when you do not wish to use DB connection pooling, such as when integrating with a multi-tenant
|
||||
/// application, where re-use of DB Context objects is impractical.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Auto-running of migrations is not supported in this scenario. When pooling is not in use and each instance of
|
||||
/// the DB Context may differ, it is not feasible to try to automatically migrate them.
|
||||
/// Your application is responsible for executing the contents of the <see cref="RunMigrations"/> class in a manner
|
||||
/// which is suitable for your use-case.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="elsa">An Elsa options builder</param>
|
||||
/// <param name="configure">A configuration builder callback</param>
|
||||
/// <param name="serviceLifetime">The service lifetime which will be used for each DB Context instance</param>
|
||||
/// <returns>The Elsa options builder, so calls may be chained</returns>
|
||||
public static ElsaOptions UseNonPooledEntityFrameworkPersistence(this ElsaOptions elsa,
|
||||
Action<DbContextOptionsBuilder> configure,
|
||||
ServiceLifetime serviceLifetime = ServiceLifetime.Singleton) =>
|
||||
elsa.UseNonPooledEntityFrameworkPersistence((_, builder) => configure(builder), serviceLifetime);
|
||||
|
||||
/// <summary>
|
||||
/// Configures Elsa to use Entity Framework Core for persistence, without using pooled DB Context instances.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Use this method when you do not wish to use DB connection pooling, such as when integrating with a multi-tenant
|
||||
/// application, where re-use of DB Context objects is impractical.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Auto-running of migrations is not supported in this scenario. When pooling is not in use and each instance of
|
||||
/// the DB Context may differ, it is not feasible to try to automatically migrate them.
|
||||
/// Your application is responsible for executing the contents of the <see cref="RunMigrations"/> class in a manner
|
||||
/// which is suitable for your use-case.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
/// <param name="elsa">An Elsa options builder</param>
|
||||
/// <param name="configure">A configuration builder callback, which also provides access to a service provider</param>
|
||||
/// <param name="serviceLifetime">The service lifetime which will be used for each DB Context instance</param>
|
||||
/// <returns>The Elsa options builder, so calls may be chained</returns>
|
||||
public static ElsaOptions UseNonPooledEntityFrameworkPersistence(this ElsaOptions elsa,
|
||||
Action<IServiceProvider, DbContextOptionsBuilder> configure,
|
||||
ServiceLifetime serviceLifetime = ServiceLifetime.Singleton) =>
|
||||
UseEntityFrameworkPersistence(elsa, configure, false, false, serviceLifetime);
|
||||
|
||||
static ElsaOptions UseEntityFrameworkPersistence(ElsaOptions elsa,
|
||||
Action<IServiceProvider, DbContextOptionsBuilder> configure,
|
||||
bool autoRunMigrations,
|
||||
bool useContextPooling,
|
||||
ServiceLifetime serviceLifetime)
|
||||
{
|
||||
/* Auto-running migrations is intentionally unavailable when not using context pooling.
|
||||
* When we aren't using pooling then it probably means that each DB Context is different
|
||||
* in some manner. That could easily mean the connection strings (IE: Contexts might not
|
||||
* all connect to the same DB). In that case, without further logic (which can't be
|
||||
* pre-empted by Elsa), we can't be sure we're connecting to the right DBs when running
|
||||
* migrations.
|
||||
*
|
||||
* It's much more sane just to explicitly not-support it and leave it to the app developer.
|
||||
* They can run their own migrations in line with their own logic.
|
||||
*/
|
||||
|
||||
if(useContextPooling)
|
||||
elsa.Services.AddPooledDbContextFactory<ElsaContext>(configure);
|
||||
else
|
||||
elsa.Services.AddDbContextFactory<ElsaContext>(configure, serviceLifetime);
|
||||
|
||||
elsa.Services
|
||||
.AddPooledDbContextFactory<ElsaContext>(configure)
|
||||
.AddScoped<EntityFrameworkWorkflowDefinitionStore>()
|
||||
.AddScoped<EntityFrameworkWorkflowInstanceStore>()
|
||||
.AddScoped<EntityFrameworkWorkflowExecutionLogRecordStore>()
|
||||
.AddScoped<EntityFrameworkBookmarkStore>();
|
||||
|
||||
if (autorunMigrations)
|
||||
if (autoRunMigrations)
|
||||
elsa.Services.AddStartupTask<RunMigrations>();
|
||||
|
||||
return elsa
|
||||
|
|
|
|||
|
|
@ -0,0 +1,28 @@
|
|||
using System.Reflection;
|
||||
using AutoFixture;
|
||||
using AutoFixture.Xunit2;
|
||||
using Elsa.Testing.Shared.AutoFixture.Customizations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Elsa.Persistence.EntityFramework.Core.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Elsa.Persistence.EntityFramework.Sqlite;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Autofixture
|
||||
{
|
||||
public class HostBuilderWithPersistableWorkflowAndEfSqliteAttribute : CustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization(ParameterInfo parameter)
|
||||
{
|
||||
return new HostBubilderUsingServicesCustomization(services => {
|
||||
services
|
||||
.AddElsa(elsa => {
|
||||
elsa
|
||||
.UseEntityFrameworkPersistence(opts => {
|
||||
opts.UseSqlite("Data Source=elsa.db;", db => db.MigrationsAssembly(typeof(SqliteElsaContextFactory).Assembly.GetName().Name));
|
||||
})
|
||||
.AddPersistableWorkflow();
|
||||
});
|
||||
}, parameter);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,23 +6,22 @@ using Elsa.Core.IntegrationTests.Workflows;
|
|||
using Elsa.Persistence.MongoDb.Extensions;
|
||||
using Elsa.Testing.Shared.AutoFixture.Customizations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Autofixture
|
||||
{
|
||||
public class HostBuilderWithElsaSampleWorkflowAndMongoDbAttribute : CustomizeAttribute
|
||||
public class HostBuilderWithPersistableWorkflowAndMongoDbAttribute : CustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization(ParameterInfo parameter)
|
||||
{
|
||||
return new HostBubilderUsingServicesCustomization(services => {
|
||||
services
|
||||
.AddElsa(elsa => {
|
||||
elsa.UseMongoDbPersistence(opts => {
|
||||
opts.ConnectionString = "mongodb://localhost:27017";
|
||||
opts.DatabaseName = "IntegrationTests";
|
||||
});
|
||||
elsa.AddActivity<UserTask>();
|
||||
elsa.AddWorkflow<PersistableWorkflow>();
|
||||
elsa
|
||||
.UseMongoDbPersistence(opts => {
|
||||
opts.ConnectionString = "mongodb://localhost:27017";
|
||||
opts.DatabaseName = "IntegrationTests";
|
||||
})
|
||||
.AddPersistableWorkflow();
|
||||
})
|
||||
;
|
||||
}, parameter);
|
||||
|
|
@ -1,24 +1,18 @@
|
|||
using System.Reflection;
|
||||
using AutoFixture;
|
||||
using AutoFixture.Xunit2;
|
||||
using Elsa.Activities.UserTask.Activities;
|
||||
using Elsa.Core.IntegrationTests.Workflows;
|
||||
using Elsa.Testing.Shared.AutoFixture.Customizations;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Autofixture
|
||||
{
|
||||
public class HostBuilderWithElsaSampleWorkflowAttribute : CustomizeAttribute
|
||||
public class HostBuilderWithPersistableWorkflowAttribute : CustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization(ParameterInfo parameter)
|
||||
{
|
||||
return new HostBubilderUsingServicesCustomization(services => {
|
||||
services
|
||||
.AddElsa(elsa => {
|
||||
elsa.AddActivity<UserTask>();
|
||||
elsa.AddWorkflow<PersistableWorkflow>();
|
||||
});
|
||||
.AddElsa(elsa => elsa.AddPersistableWorkflow());
|
||||
}, parameter);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
using System.Reflection;
|
||||
using AutoFixture;
|
||||
using AutoFixture.Xunit2;
|
||||
using Elsa.Testing.Shared.AutoFixture.Customizations;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Autofixture
|
||||
{
|
||||
public class StubElsaContextAttribute : CustomizeAttribute
|
||||
{
|
||||
public override ICustomization GetCustomization(ParameterInfo parameter)
|
||||
=> new StubElsaContextCustomization(parameter);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
using Elsa.Activities.UserTask.Activities;
|
||||
using Elsa.Core.IntegrationTests.Workflows;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Autofixture
|
||||
{
|
||||
public static class TestingElsaOptionsExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Adds the minimal activities and workflow applicable to running the <see cref="PersistableWorkflow"/>.
|
||||
/// </summary>
|
||||
/// <param name="elsa">An elsa options</param>
|
||||
/// <returns>The same else options, so calls may be chained</returns>
|
||||
public static ElsaOptions AddPersistableWorkflow(this ElsaOptions elsa)
|
||||
{
|
||||
elsa.AddActivity<UserTask>();
|
||||
elsa.AddWorkflow<PersistableWorkflow>();
|
||||
return elsa;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +1,55 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<IsPackable>false</IsPackable>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofixture" Version="4.15.0" />
|
||||
<PackageReference Include="Autofixture.AutoMoq" Version="4.15.0" />
|
||||
<PackageReference Include="autofixture.xunit2" Version="4.15.0" />
|
||||
<PackageReference Include="Moq" Version="4.16.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
|
||||
<PackageReference Include="NodaTime.Testing" Version="3.0.3" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="XunitXml.TestLogger" Version="3.0.62" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="YesSql.Core" Version="2.0.0-beta-1637" />
|
||||
<PackageReference Include="YesSql.Provider.Sqlite" Version="1.0.0-beta-1637" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.19" />
|
||||
<PackageReference Include="Hangfire.InMemory" Version="0.3.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Autofixture" Version="4.15.0" />
|
||||
<PackageReference Include="Autofixture.AutoMoq" Version="4.15.0" />
|
||||
<PackageReference Include="autofixture.xunit2" Version="4.15.0" />
|
||||
<PackageReference Include="Moq" Version="4.16.0" />
|
||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.8.3" />
|
||||
<PackageReference Include="NodaTime.Testing" Version="3.0.3" />
|
||||
<PackageReference Include="xunit" Version="2.4.1" />
|
||||
<PackageReference Include="XunitXml.TestLogger" Version="3.0.62" />
|
||||
<PackageReference Include="xunit.runner.visualstudio" Version="2.4.3">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="coverlet.collector" Version="3.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="YesSql.Core" Version="2.0.0-beta-1637" />
|
||||
<PackageReference Include="YesSql.Provider.Sqlite" Version="1.0.0-beta-1637" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
||||
<PackageReference Include="Hangfire.AspNetCore" Version="1.7.19" />
|
||||
<PackageReference Include="Hangfire.InMemory" Version="0.3.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Update="Elsa.Core.UnitTests.xunit.runner.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Assets\hello-world-workflow.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Update="Elsa.Core.UnitTests.xunit.runner.json">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</None>
|
||||
<None Update="Assets\hello-world-workflow.json">
|
||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\core\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\shared\Elsa.Testing.Shared\Elsa.Testing.Shared.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\activities\Elsa.Activities.Temporal.Common\Elsa.Activities.Temporal.Common.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\activities\Elsa.Activities.Temporal.Hangfire\Elsa.Activities.Temporal.Hangfire.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\activities\Elsa.Activities.Temporal.Quartz\Elsa.Activities.Temporal.Quartz.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\persistence\Elsa.Persistence.MongoDb\Elsa.Persistence.MongoDb.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\persistence\Elsa.Persistence.YesSql\Elsa.Persistence.YesSql.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\persistence\Elsa.Persistence.EntityFramework\Elsa.Persistence.EntityFramework.Core\Elsa.Persistence.EntityFramework.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\persistence\Elsa.Persistence.EntityFramework\Elsa.Persistence.EntityFramework.Sqlite\Elsa.Persistence.EntityFramework.Sqlite.csproj" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\core\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\shared\Elsa.Testing.Shared\Elsa.Testing.Shared.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\activities\Elsa.Activities.Temporal.Common\Elsa.Activities.Temporal.Common.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\activities\Elsa.Activities.Temporal.Hangfire\Elsa.Activities.Temporal.Hangfire.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\activities\Elsa.Activities.Temporal.Quartz\Elsa.Activities.Temporal.Quartz.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\persistence\Elsa.Persistence.MongoDb\Elsa.Persistence.MongoDb.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\persistence\Elsa.Persistence.YesSql\Elsa.Persistence.YesSql.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\persistence\Elsa.Persistence.EntityFramework\Elsa.Persistence.EntityFramework.Core\Elsa.Persistence.EntityFramework.Core.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\persistence\Elsa.Persistence.EntityFramework\Elsa.Persistence.EntityFramework.Sqlite\Elsa.Persistence.EntityFramework.Sqlite.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,100 @@
|
|||
using Xunit;
|
||||
using System.Threading.Tasks;
|
||||
using Elsa.Core.IntegrationTests.Autofixture;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using System.Threading;
|
||||
using Elsa.Services;
|
||||
using Elsa.Core.IntegrationTests.Workflows;
|
||||
using Elsa.Persistence;
|
||||
using Elsa.Persistence.EntityFramework.Core.Extensions;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Elsa.Persistence.EntityFramework.Core;
|
||||
using Microsoft.EntityFrameworkCore.Internal;
|
||||
using Moq;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Persistence.EntityFramework
|
||||
{
|
||||
public class EntityFrameworkIntegrationTests
|
||||
{
|
||||
[Theory(DisplayName = "A persistable workflow instance with default persistence behaviour should be persisted-to and readable-from an Entity Framework store after being run"), AutoMoqData]
|
||||
public async Task APersistableWorkflowInstanceWithDefaultPersistanceBehaviourShouldBeRoundTrippable([HostBuilderWithPersistableWorkflowAndEfSqlite] IHostBuilder hostBuilder)
|
||||
{
|
||||
hostBuilder.ConfigureServices((ctx, services) => services.AddHostedService<HostedWorkflowRunner>());
|
||||
var host = await hostBuilder.StartAsync();
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "A resolved context should come from the pool when set up with pooling"), AutoMoqData]
|
||||
public void DbContextShouldBeCreatedFromPoolWhenSetUpWithPooling(ServiceCollection serviceCollection,
|
||||
[StubElsaContext] ElsaContext pooledContext,
|
||||
IDbContextPool<ElsaContext> pool)
|
||||
{
|
||||
serviceCollection
|
||||
.AddElsa(elsa => {
|
||||
elsa
|
||||
.UseEntityFrameworkPersistence(opts => {
|
||||
opts.UseSqlite("Data Source=:memory:;Mode=Memory;");
|
||||
});
|
||||
})
|
||||
.AddSingleton(pool);
|
||||
|
||||
Mock.Get(pool).Setup(x => x.Rent()).Returns(pooledContext);
|
||||
|
||||
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
var contextFactory = serviceProvider.GetRequiredService<IDbContextFactory<ElsaContext>>();
|
||||
|
||||
using(var context = contextFactory.CreateDbContext())
|
||||
{
|
||||
Assert.Same(pooledContext, context);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory(DisplayName = "A resolved context should not come from the pool when set up without pooling"), AutoMoqData]
|
||||
public void DbContextShouldNotBeCreatedFromPoolWhenSetUpWithoutPooling(ServiceCollection serviceCollection,
|
||||
[StubElsaContext] ElsaContext pooledContext,
|
||||
IDbContextPool<ElsaContext> pool)
|
||||
{
|
||||
serviceCollection
|
||||
.AddElsa(elsa => {
|
||||
elsa
|
||||
.UseNonPooledEntityFrameworkPersistence((services, opts) => {
|
||||
opts.UseSqlite("Data Source=:memory:;Mode=Memory;");
|
||||
},
|
||||
ServiceLifetime.Transient);
|
||||
})
|
||||
.AddSingleton(pool);
|
||||
|
||||
Mock.Get(pool).Setup(x => x.Rent()).Returns(pooledContext);
|
||||
|
||||
var serviceProvider = serviceCollection.BuildServiceProvider();
|
||||
var contextFactory = serviceProvider.GetRequiredService<IDbContextFactory<ElsaContext>>();
|
||||
|
||||
using(var context = contextFactory.CreateDbContext())
|
||||
{
|
||||
Assert.NotSame(pooledContext, context);
|
||||
}
|
||||
}
|
||||
|
||||
class HostedWorkflowRunner : IHostedService
|
||||
{
|
||||
readonly IWorkflowRunner workflowRunner;
|
||||
readonly IWorkflowInstanceStore instanceStore;
|
||||
|
||||
public async Task StartAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
var instance = await workflowRunner.RunWorkflowAsync<PersistableWorkflow>();
|
||||
var retrievedInstance = await instanceStore.FindByIdAsync(instance.Id);
|
||||
|
||||
Assert.NotNull(retrievedInstance);
|
||||
}
|
||||
|
||||
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
|
||||
|
||||
public HostedWorkflowRunner(IWorkflowRunner workflowRunner, IWorkflowInstanceStore instanceStore)
|
||||
{
|
||||
this.workflowRunner = workflowRunner ?? throw new System.ArgumentNullException(nameof(workflowRunner));
|
||||
this.instanceStore = instanceStore ?? throw new System.ArgumentNullException(nameof(instanceStore));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -13,7 +13,7 @@ namespace Elsa.Core.IntegrationTests.Persistence.InMemory
|
|||
public class InMemoryStoreIntegrationTests
|
||||
{
|
||||
[Theory(DisplayName = "A persistable workflow instance with default persistence behaviour should be persisted-to and readable-from an in-memory store after being run"), AutoMoqData]
|
||||
public async Task APersistableWorkflowInstanceWithDefaultPersistanceBehaviourShouldBeRoundTrippable([HostBuilderWithElsaSampleWorkflow] IHostBuilder hostBuilder)
|
||||
public async Task APersistableWorkflowInstanceWithDefaultPersistanceBehaviourShouldBeRoundTrippable([HostBuilderWithPersistableWorkflow] IHostBuilder hostBuilder)
|
||||
{
|
||||
hostBuilder.ConfigureServices((ctx, services) => {
|
||||
services.AddHostedService<HostedWorkflowRunner<PersistableWorkflow>>();
|
||||
|
|
@ -22,7 +22,7 @@ namespace Elsa.Core.IntegrationTests.Persistence.InMemory
|
|||
}
|
||||
|
||||
[Theory(DisplayName = "A persistable-on-suspend workflow instance should be persisted-to and readable-from an in-memory store after being run"), AutoMoqData]
|
||||
public async Task APersistableOnSuspendWorkflowInstanceShouldBeRoundTrippable([HostBuilderWithElsaSampleWorkflow] IHostBuilder hostBuilder)
|
||||
public async Task APersistableOnSuspendWorkflowInstanceShouldBeRoundTrippable([HostBuilderWithPersistableWorkflow] IHostBuilder hostBuilder)
|
||||
{
|
||||
hostBuilder.ConfigureServices((ctx, services) => {
|
||||
services.AddHostedService<HostedWorkflowRunner<PersistableWorkflow.OnSuspend>>();
|
||||
|
|
@ -31,7 +31,7 @@ namespace Elsa.Core.IntegrationTests.Persistence.InMemory
|
|||
}
|
||||
|
||||
[Theory(DisplayName = "A persistable-on-activity-executed workflow instance should be persisted-to and readable-from an in-memory store after being run"), AutoMoqData]
|
||||
public async Task APersistableOnActivityExecutedWorkflowInstanceShouldBeRoundTrippable([HostBuilderWithElsaSampleWorkflow] IHostBuilder hostBuilder)
|
||||
public async Task APersistableOnActivityExecutedWorkflowInstanceShouldBeRoundTrippable([HostBuilderWithPersistableWorkflow] IHostBuilder hostBuilder)
|
||||
{
|
||||
hostBuilder.ConfigureServices((ctx, services) => {
|
||||
services.AddHostedService<HostedWorkflowRunner<PersistableWorkflow.OnActivityExecuted>>();
|
||||
|
|
@ -40,7 +40,7 @@ namespace Elsa.Core.IntegrationTests.Persistence.InMemory
|
|||
}
|
||||
|
||||
[Theory(DisplayName = "A persistable-on-workflow-burst workflow instance should be persisted-to and readable-from an in-memory store after being run"), AutoMoqData]
|
||||
public async Task APersistableOnWorkflowBurstWorkflowInstanceShouldBeRoundTrippable([HostBuilderWithElsaSampleWorkflow] IHostBuilder hostBuilder)
|
||||
public async Task APersistableOnWorkflowBurstWorkflowInstanceShouldBeRoundTrippable([HostBuilderWithPersistableWorkflow] IHostBuilder hostBuilder)
|
||||
{
|
||||
hostBuilder.ConfigureServices((ctx, services) => {
|
||||
services.AddHostedService<HostedWorkflowRunner<PersistableWorkflow.OnWorkflowBurst>>();
|
||||
|
|
@ -49,7 +49,7 @@ namespace Elsa.Core.IntegrationTests.Persistence.InMemory
|
|||
}
|
||||
|
||||
[Theory(DisplayName = "A persistable-on-workflow-pass-completed workflow instance should be persisted-to and readable-from an in-memory store after being run"), AutoMoqData]
|
||||
public async Task APersistableOnWorkflowPassCompletedWorkflowInstanceShouldBeRoundTrippable([HostBuilderWithElsaSampleWorkflow] IHostBuilder hostBuilder)
|
||||
public async Task APersistableOnWorkflowPassCompletedWorkflowInstanceShouldBeRoundTrippable([HostBuilderWithPersistableWorkflow] IHostBuilder hostBuilder)
|
||||
{
|
||||
hostBuilder.ConfigureServices((ctx, services) => {
|
||||
services.AddHostedService<HostedWorkflowRunner<PersistableWorkflow.OnWorkflowPassCompleted>>();
|
||||
|
|
|
|||
|
|
@ -7,14 +7,13 @@ using System.Threading;
|
|||
using Elsa.Services;
|
||||
using Elsa.Core.IntegrationTests.Workflows;
|
||||
using Elsa.Persistence;
|
||||
using System;
|
||||
|
||||
namespace Elsa.Core.IntegrationTests.Persistence.MongoDb
|
||||
{
|
||||
public class MongoDbIntegrationTests
|
||||
{
|
||||
[Theory(DisplayName = "A persistable workflow instance with default persistence behaviour should be persisted-to and readable-from a MongoDb store after being run"), AutoMoqData]
|
||||
public async Task APersistableWorkflowInstanceWithDefaultPersistanceBehaviourShouldBeRoundTrippable([HostBuilderWithElsaSampleWorkflowAndMongoDbAttribute] IHostBuilder hostBuilder)
|
||||
public async Task APersistableWorkflowInstanceWithDefaultPersistanceBehaviourShouldBeRoundTrippable([HostBuilderWithPersistableWorkflowAndMongoDbAttribute] IHostBuilder hostBuilder)
|
||||
{
|
||||
hostBuilder.ConfigureServices((ctx, services) => services.AddHostedService<HostedWorkflowRunner>());
|
||||
var host = await hostBuilder.StartAsync();
|
||||
|
|
|
|||
|
|
@ -7,31 +7,17 @@ using Microsoft.Extensions.DependencyInjection;
|
|||
|
||||
namespace Elsa.Testing.Shared.AutoFixture.Customizations
|
||||
{
|
||||
public class HostBubilderUsingServicesCustomization : ICustomization
|
||||
public class HostBubilderUsingServicesCustomization : SpecimenBuilderForParameterCustomization
|
||||
{
|
||||
readonly Action<IServiceCollection> serviceConfig;
|
||||
readonly ParameterInfo? parameter;
|
||||
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customizations.Insert(0, GetSpecimenBuilder());
|
||||
}
|
||||
|
||||
ISpecimenBuilder GetSpecimenBuilder()
|
||||
{
|
||||
var specimenBuilder = new HostBubilderUsingServicesSpecimenBuilder(serviceConfig);
|
||||
if(parameter is null) return specimenBuilder;
|
||||
|
||||
var paramSpec = new ParameterSpecification(parameter.ParameterType, parameter.Name);
|
||||
return new FilteringSpecimenBuilder(specimenBuilder, paramSpec);
|
||||
}
|
||||
protected override ISpecimenBuilder GetUnfilteredSpecimenBuilder()
|
||||
=> new HostBubilderUsingServicesSpecimenBuilder(serviceConfig);
|
||||
|
||||
public HostBubilderUsingServicesCustomization(Action<IServiceCollection> serviceConfig,
|
||||
ParameterInfo? parameter = null)
|
||||
ParameterInfo? parameter) : base(parameter)
|
||||
{
|
||||
this.parameter = parameter ?? throw new ArgumentNullException(nameof(parameter));
|
||||
this.serviceConfig = serviceConfig ?? throw new ArgumentNullException(nameof(serviceConfig));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using System.Reflection;
|
||||
using AutoFixture;
|
||||
using AutoFixture.Kernel;
|
||||
|
||||
namespace Elsa.Testing.Shared.AutoFixture.Customizations
|
||||
{
|
||||
/// <summary>
|
||||
/// Helper base class for creating an Autofixture customization which customizes
|
||||
/// a single parameter value with a specified specimen builder.
|
||||
/// </summary>
|
||||
public abstract class SpecimenBuilderForParameterCustomization : ICustomization
|
||||
{
|
||||
readonly ParameterInfo? parameter;
|
||||
|
||||
public void Customize(IFixture fixture)
|
||||
{
|
||||
fixture.Customizations.Insert(0, GetSpecimenBuilder());
|
||||
}
|
||||
|
||||
protected virtual ISpecimenBuilder GetSpecimenBuilder()
|
||||
{
|
||||
var unfilteredSpecimenBuilder = GetUnfilteredSpecimenBuilder();
|
||||
if(parameter is null) return unfilteredSpecimenBuilder;
|
||||
|
||||
var paramSpec = new ParameterSpecification(parameter.ParameterType, parameter.Name);
|
||||
return new FilteringSpecimenBuilder(unfilteredSpecimenBuilder, paramSpec);
|
||||
}
|
||||
|
||||
protected abstract ISpecimenBuilder GetUnfilteredSpecimenBuilder();
|
||||
|
||||
public SpecimenBuilderForParameterCustomization(ParameterInfo? parameter)
|
||||
{
|
||||
this.parameter = parameter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
using System.Reflection;
|
||||
using AutoFixture;
|
||||
using AutoFixture.Kernel;
|
||||
using Elsa.Testing.Shared.AutoFixture.SpecimenBuilders;
|
||||
|
||||
namespace Elsa.Testing.Shared.AutoFixture.Customizations
|
||||
{
|
||||
public class StubElsaContextCustomization : SpecimenBuilderForParameterCustomization
|
||||
{
|
||||
protected override ISpecimenBuilder GetUnfilteredSpecimenBuilder()
|
||||
=> new StubElsaContextSpecimenBuilder();
|
||||
|
||||
public StubElsaContextCustomization(ParameterInfo? parameter) : base(parameter) {}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using AutoFixture.Kernel;
|
||||
using Elsa.Persistence.EntityFramework.Core;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Elsa.Testing.Shared.AutoFixture.SpecimenBuilders
|
||||
{
|
||||
public class StubElsaContextSpecimenBuilder : ISpecimenBuilder
|
||||
{
|
||||
public object Create(object request, ISpecimenContext context)
|
||||
=> request.IsAnAutofixtureRequestForType<ElsaContext>()? GetContext(context) : new NoSpecimen();
|
||||
|
||||
static object GetContext(ISpecimenContext context)
|
||||
{
|
||||
var options = (DbContextOptions<ElsaContext>) context.Resolve(typeof(DbContextOptions<ElsaContext>));
|
||||
return new ElsaContext(options);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,10 +20,12 @@
|
|||
<PackageReference Include="NodaTime" Version="3.0.3" />
|
||||
<PackageReference Include="xunit.extensibility.core" Version="2.4.1" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="5.0.0" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="5.0.2" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\src\core\Elsa\Elsa.csproj" />
|
||||
<ProjectReference Include="..\..\..\src\persistence\Elsa.Persistence.EntityFramework\Elsa.Persistence.EntityFramework.Core\Elsa.Persistence.EntityFramework.Core.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
|
|
|||
Loading…
Reference in a new issue