diff --git a/Directory.Packages.props b/Directory.Packages.props index 871b228569..9a1fcbb674 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -137,7 +137,7 @@ - + diff --git a/docs/en/framework/data/entity-framework-core/index.md b/docs/en/framework/data/entity-framework-core/index.md index 208baeefda..418c040565 100644 --- a/docs/en/framework/data/entity-framework-core/index.md +++ b/docs/en/framework/data/entity-framework-core/index.md @@ -146,7 +146,7 @@ Configure(options => }); ```` -Add actions for the `ConfigureConventions` and `OnModelCreating` methods of the `DbContext` as shown below: +Add actions for the `ConfigureConventions`, `OnModelCreating` and `OnConfiguring` methods of the `DbContext` as shown below: ````csharp Configure(options => @@ -170,6 +170,15 @@ Configure(options => { // This action is called for OnModelCreating method of specific DbContext. }); + + options.ConfigureDefaultOnConfiguring((dbContext, optionsBuilder) => + { + // This action is called for OnConfiguring method of all DbContexts. + }); + options.ConfigureOnConfiguring((dbContext, optionsBuilder) => + { + // This action is called for OnConfiguring method of specific DbContext. + }); }); ```` diff --git a/docs/en/images/elsa-studio-wasm.png b/docs/en/images/elsa-studio-wasm.png new file mode 100644 index 0000000000..9365ef6a4c Binary files /dev/null and b/docs/en/images/elsa-studio-wasm.png differ diff --git a/docs/en/modules/ai-management/index.md b/docs/en/modules/ai-management/index.md index 07f1cf2da7..ad75029b8b 100644 --- a/docs/en/modules/ai-management/index.md +++ b/docs/en/modules/ai-management/index.md @@ -3,8 +3,7 @@ > You must have an ABP Team or a higher license to use this module. > **⚠️ Important Notice** -> The **AI Management Module** is currently in **preview** and not yet production-ready. The documentation and implementation are subject to change. -> We recommend using this module for **evaluation and experimentation** only, not in production environments for now. +> The **AI Management Module** is currently in **preview**. The documentation and implementation are subject to change. This module implements AI (Artificial Intelligence) management capabilities on top of the [Artificial Intelligence Workspaces](../../framework/infrastructure/artificial-intelligence/index.md) feature of the ABP Framework and allows to manage workspaces dynamically from the application including UI components and API endpoints. @@ -130,7 +129,7 @@ PreConfigure(options => #### Dynamic Workspaces -* **Created through the UI** or programmatically via `IWorkspaceRepository` +* **Created through the UI** or programmatically via `ApplicationWorkspaceManager` and `IWorkspaceRepository` * **Fully manageable** - can be created, updated, activated/deactivated, and deleted * **Stored in database** with all configuration * **Ideal for** user-customizable AI features @@ -138,15 +137,30 @@ PreConfigure(options => Example (data seeding): ```csharp -var workspace = new Workspace( - name: "CustomerSupportWorkspace", - provider: "OpenAI", - modelName: "gpt-4", - apiKey: "your-api-key" -); -workspace.ApplicationName = ApplicationInfoAccessor.ApplicationName; -workspace.SystemPrompt = "You are a helpful customer support assistant."; -await _workspaceRepository.InsertAsync(workspace); +public class WorkspaceDataSeederContributor : IDataSeedContributor, ITransientDependency +{ + private readonly IWorkspaceRepository _workspaceRepository; + private readonly ApplicationWorkspaceManager _applicationWorkspaceManager; + public WorkspaceDataSeederContributor( + IWorkspaceRepository workspaceRepository, + ApplicationWorkspaceManager applicationWorkspaceManager) + { + _workspaceRepository = workspaceRepository; + _applicationWorkspaceManager = applicationWorkspaceManager; + } + + public async Task SeedAsync(DataSeedContext context) + { + var workspace = await _applicationWorkspaceManager.CreateAsync( + name: "CustomerSupportWorkspace", + provider: "OpenAI", + modelName: "gpt-4"); + + workspace.ApiKey = "your-api-key"; + workspace.SystemPrompt = "You are a helpful customer support assistant."; + + await _workspaceRepository.InsertAsync(workspace); + } ``` ### Workspace Naming Rules @@ -172,12 +186,13 @@ The AI Management module defines the following permissions: In addition to module-level permissions, you can restrict access to individual workspaces by setting the `RequiredPermissionName` property: ```csharp -var workspace = new Workspace( +var workspace = await _applicationWorkspaceManager.CreateAsync( name: "PremiumWorkspace", provider: "OpenAI", - modelName: "gpt-4", - requiredPermissionName: "MyApp.PremiumFeatures" + modelName: "gpt-4" ); +// Set a specific permission for the workspace +workspace.RequiredPermissionName = MyAppPermissions.AccessPremiumWorkspaces; ``` When a workspace has a required permission: @@ -430,6 +445,37 @@ Your application acts as a proxy, forwarding these requests to the AI Management | **3. Client Remote** | No | Remote Service | Remote Service | No | Microservices consuming AI centrally | | **4. Client Proxy** | No | Remote Service | Remote Service | Yes | API Gateway pattern, proxy services | + +## Using Dynamic Workspace Configurations for custom requirements +The AI Management module allows you to access only configuration of a workspace without resolving pre-constructed chat client. This is useful when you want to use a workspace for your own purposes and you don't need to use the chat client. +The `IWorkspaceConfigurationStore` service is used to access the configuration of a workspace. It has multiple implementaations according to the usage scenario. + +```csharp +public class MyService +{ + private readonly IWorkspaceConfigurationStore _workspaceConfigurationStore; + public MyService(IWorkspaceConfigurationStore workspaceConfigurationStore) + { + _workspaceConfigurationStore = workspaceConfigurationStore; + } + + public async Task DoSomethingAsync() + { + // Get the configuration of the workspace that can be managed dynamically. + var configuration = await _workspaceConfigurationStore.GetAsync("MyWorkspace"); + + // Do something with the configuration + var kernel = Kernel.CreateBuilder() + .AddAzureOpenAIChatClient( + config.ModelName!, + new Uri(config.ApiBaseUrl), + config.ApiKey + ) + .Build(); + } +} +``` + ## Implementing Custom AI Provider Factories While the AI Management module provides built-in support for OpenAI through the `Volo.AIManagement.OpenAI` package, you can easily add support for other AI providers by implementing a custom `IChatClientFactory`. @@ -558,14 +604,14 @@ After implementing and registering your factory: 2. **Through Code** (data seeding): ```csharp -await _workspaceRepository.InsertAsync(new Workspace( - GuidGenerator.Create(), - "MyOllamaWorkspace", - provider: "Ollama", - modelName: "mistral", - apiBaseUrl: "http://localhost:11434", - description: "Local Ollama workspace" -)); +var workspace = await _applicationWorkspaceManager.CreateAsync( + name: "MyOllamaWorkspace", + provider: "Ollama", + modelName: "mistral" +); +workspace.ApiBaseUrl = "http://localhost:11434"; +workspace.Description = "Local Ollama workspace"; +await _workspaceRepository.InsertAsync(workspace); ``` > **Tip**: The provider name you use in `AddFactory("ProviderName")` must match the provider name stored in the workspace configuration in the database. @@ -589,7 +635,7 @@ The following custom repositories are defined: #### Domain Services - `ApplicationWorkspaceManager`: Manages workspace operations and validations. -- `WorkspaceConfigurationStore`: Retrieves workspace configuration with caching. +- `WorkspaceConfigurationStore`: Retrieves workspace configuration with caching. Implements `IWorkspaceConfigurationStore` interface. - `ChatClientResolver`: Resolves the appropriate `IChatClient` implementation for a workspace. #### Integration Services @@ -618,6 +664,9 @@ Workspace configurations are cached for performance. The cache key format: WorkspaceConfiguration:{ApplicationName}:{WorkspaceName} ``` +### HttpApi Client Layer +- `IntegrationWorkspaceConfigurationStore`: Integration service for remote workspace configuration retrieval. Implements `IWorkspaceConfigurationStore` interface. + The cache is automatically invalidated when workspaces are created, updated, or deleted. ## See Also diff --git a/docs/en/modules/elsa-pro.md b/docs/en/modules/elsa-pro.md index 3a6ec4533b..f070cbf8d9 100644 --- a/docs/en/modules/elsa-pro.md +++ b/docs/en/modules/elsa-pro.md @@ -4,7 +4,7 @@ This module integrates [Elsa Workflows](https://docs.elsaworkflows.io/) into ABP Framework applications and is designed to make it easy for developers to use Elsa's capabilities within their ABP-based projects. For creating, managing, and customizing workflows themselves, please refer to [the official Elsa documentation](https://docs.elsaworkflows.io/). -## How to install +## How to Install The Elsa module is not installed in [the startup templates](../solution-templates/layered-web-application) by default and must be installed manually. There are two ways of installing a module into your application and each one of these approaches is explained in the next sections. @@ -37,6 +37,23 @@ After adding the package references, open the module class of the project (e.g.: > If you are using Blazor Web App, you need to add the `Volo.Elsa.Admin.Blazor.WebAssembly` package to the **{ProjectName}.Blazor.Client.csproj** project and add the `Volo.Elsa.Admin.Blazor.Server` package to the **{ProjectName}.Blazor.csproj** project. +### `AbpElsaAspNetCoreModule` and `AbpElsaIdentityModule` + +These two modules generally will be added to your authentication project. Please add `Volo.Elsa.Abp.AspNetCore` and `Volo.Elsa.Abp.Identity` packages to your project and add the `AbpElsaAspNetCoreModule` and `AbpElsaIdentityModule` to the `DependsOn` attribute of your module class based on your project structure: + +```xml + + +``` + +```csharp +[DependsOn( + //... + typeof(AbpElsaAspNetCoreModule), + typeof(AbpElsaIdentityModule) +)] +``` + ## The Elsa Module The Elsa Workflows has its own database provider, and also has a Tenant/Role/User system. They are under active development, so the ABP Elsa module is not yet fully integrated. Below is the current status of each module in the ABP's Elsa Module: @@ -56,6 +73,49 @@ The rest of the projects/modules are basically empty and will be implemented in - `AbpElsaBlazorWebAssemblyModule(Volo.Elsa.Abp.Blazor.WebAssembly)` - `AbpElsaWebModule(Volo.Elsa.Abp.Web)` +## Configure the Elsa Server + +You need to configure Elsa in your ABP application to use its features. You can do that in the `ConfigureServices` method of your `YourElsaAppModule` class as shown below: + +> For more information about configuring Elsa, please refer to [the official Elsa documentation](https://docs.elsaworkflows.io/). + +```cs +private void ConfigureElsa(ServiceConfigurationContext context, IConfiguration configuration) +{ + var connectionString = configuration.GetConnectionString("Default")!; + context.Services + .AddElsa(elsa => elsa + .UseAbpIdentity(identity => // Use UseAbpIdentity instead of UseIdentity to integrate with ABP Identity module + { + identity.TokenOptions = options => options.SigningKey = "large-signing-key-for-signing-JWT-tokens"; + }) + .UseWorkflowManagement(management => management.UseEntityFrameworkCore(ef => ef.UseSqlServer(connectionString))) + .UseWorkflowRuntime(runtime => runtime.UseEntityFrameworkCore(ef => ef.UseSqlServer(connectionString))) + .UseScheduling() + .UseJavaScript() + .UseLiquid() + .UseCSharp() + .UseHttp(http => http.ConfigureHttpOptions = options => configuration.GetSection("Http").Bind(options)) + .UseWorkflowsApi() + .AddActivitiesFrom() + .AddWorkflowsFrom() + ); +} +``` + +## Elsa Database Migration + +Elsa module uses its own database context and migration system, ABP Elsa module doesn't contain any `aggregate root/entity` at the moment. So, **you don't need to create any initial migration for Elsa module**. You just need to configure the Elsa Services as follows: + +```cs +.UseWorkflowManagement(management => management.UseEntityFrameworkCore(ef => ef.UseSqlServer(connectionString))) +.UseWorkflowRuntime(runtime => runtime.UseEntityFrameworkCore(ef => ef.UseSqlServer(connectionString))) +``` + +When you run your application, Elsa will create its own database tables if they do not exist. + +> See [how to configure Elsa Workflows to use different database providers for persistence, including SQL Server, PostgreSQL, and MongoDB](https://docs.elsaworkflows.io/getting-started/database-configuration) for more information. + ### Elsa Module Permissions The Elsa Workflow API endpoints check permissions. Also, it has a `*` wildcard permission to allow all permissions. @@ -72,14 +132,24 @@ You can also grant parts of the permissions to a role or user. It will add the ` ### Elsa Studio -Elsa Studio is an **independent** web application that allows you to design, manage, and execute workflows. It is built using **Blazor Server/WebAssembly**. +[Elsa Studio](https://docs.elsaworkflows.io/application-types/elsa-studio) is a **standalone** web application that allows you to design, manage, and execute workflows. It is built using **Blazor Server/WebAssembly**. + +`ElsaDemoApp.Studio.WASM` is a sample Blazor WebAssembly project that demonstrates how to use Elsa Studio with ELSA Server with ABP Framework. + +> Elsa Studio has its own layout and theme, and you can't integrate it into an ABP Blazor project for now. + +![Elsa Studio](../images/elsa-studio-wasm.png) + +Please check the [Elsa Workflows - Sample Workflow Demo](../samples/elsa-workflows-demo.md) document to download its source code for review. + +#### Elsa Studio Authentication Elsa Studio requires authentication and there are two ways to authenticate Elsa Studio: * Password Flow Authentication * Code Flow Authentication -#### Elsa Studio - Password Flow Authentication +##### Elsa Studio - Password Flow Authentication The `AbpElsaIdentityModule(Volo.Elsa.Abp.Identity)` module is used to integrate with [ABP Identity module](./identity-pro.md) to check Elsa Studio *username* and *password* against ABP Identity. @@ -109,7 +179,7 @@ Once, you logged in to the application, you can start defining workflows, manage ![elsa-main](../images/elsa-main-page.png) -#### Elsa Studio - Code Flow Authentication +##### Elsa Studio - Code Flow Authentication ABP applications use [OpenIddict](./openiddict-pro.md) for authentication. So, you can use the [Authorization Code Flow](https://oauth.net/2/grant-types/authorization-code/) to authenticate Elsa Studio. diff --git a/docs/en/modules/identity-pro.md b/docs/en/modules/identity-pro.md index a2325552df..7392590c3f 100644 --- a/docs/en/modules/identity-pro.md +++ b/docs/en/modules/identity-pro.md @@ -434,8 +434,8 @@ This module doesn't define any additional distributed event. See the [standard d ## See Also * [Import External Users](./identity/import-external-users.md) -* [LDAP Login](./identity/idap.md) +* [LDAP Login](./identity/ldap.md) * [OAuth Login](./identity/oauth-login.md) * [Periodic Password Change (Password Aging)](./identity/periodic-password-change.md) * [Two Factor Authentication](./identity/two-factor-authentication.md) -* [Session Management](./identity/session-management.md) \ No newline at end of file +* [Session Management](./identity/session-management.md) diff --git a/docs/en/modules/identity/idap.md b/docs/en/modules/identity/ldap.md similarity index 100% rename from docs/en/modules/identity/idap.md rename to docs/en/modules/identity/ldap.md diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/TelemetryService.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/TelemetryService.cs index 2f684acd29..4f8f77b245 100644 --- a/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/TelemetryService.cs +++ b/framework/src/Volo.Abp.Core/Volo/Abp/Internal/Telemetry/TelemetryService.cs @@ -67,7 +67,7 @@ public class TelemetryService : ITelemetryService, IScopedDependency private Task AddActivityAsync(ActivityContext context) { - _ = Task.Run(async () => + var telemetryTask = Task.Run(async () => { using var scope = _serviceScopeFactory.CreateScope(); @@ -81,6 +81,30 @@ public class TelemetryService : ITelemetryService, IScopedDependency telemetryActivitySender); }); + AppDomain.CurrentDomain.ProcessExit += (_, _) => + { + try + { + telemetryTask.Wait(TimeSpan.FromSeconds(10)); + } + catch + { + // ignored + } + }; + + Console.CancelKeyPress += (_, _) => + { + try + { + telemetryTask.Wait(TimeSpan.FromSeconds(10)); + } + catch + { + // ignored + } + }; + return Task.CompletedTask; } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo.Abp.EntityFrameworkCore.Oracle.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo.Abp.EntityFrameworkCore.Oracle.csproj index 7a4dbe61b1..8fd893c6c1 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo.Abp.EntityFrameworkCore.Oracle.csproj +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Oracle/Volo.Abp.EntityFrameworkCore.Oracle.csproj @@ -22,8 +22,6 @@ - - diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/AbpSqliteOptions.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/AbpSqliteOptions.cs new file mode 100644 index 0000000000..2d94b2864a --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/AbpSqliteOptions.cs @@ -0,0 +1,6 @@ +namespace Volo.Abp.EntityFrameworkCore; + +public class AbpSqliteOptions +{ + public int? BusyTimeout { get; set; } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/Interceptors/SqliteBusyTimeoutSaveChangesInterceptor.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/Interceptors/SqliteBusyTimeoutSaveChangesInterceptor.cs new file mode 100644 index 0000000000..32b4859bbb --- /dev/null +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/Interceptors/SqliteBusyTimeoutSaveChangesInterceptor.cs @@ -0,0 +1,39 @@ +using System.Threading; +using System.Threading.Tasks; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Diagnostics; + +namespace Volo.Abp.EntityFrameworkCore.Interceptors; + +/// +/// https://github.com/dotnet/efcore/issues/29514 +/// +public class SqliteBusyTimeoutSaveChangesInterceptor : SaveChangesInterceptor +{ + private readonly string _pragmaCommand; + + public SqliteBusyTimeoutSaveChangesInterceptor(int timeoutMilliseconds) + { + _pragmaCommand = $"PRAGMA busy_timeout={timeoutMilliseconds};"; + } + + public override InterceptionResult SavingChanges(DbContextEventData eventData, InterceptionResult result) + { + if (eventData.Context != null) + { + eventData.Context.Database.ExecuteSqlRaw(_pragmaCommand); + } + + return result; + } + + public override async ValueTask> SavingChangesAsync(DbContextEventData eventData, InterceptionResult result, CancellationToken cancellationToken = default) + { + if (eventData.Context != null) + { + await eventData.Context.Database.ExecuteSqlRawAsync(_pragmaCommand, cancellationToken: cancellationToken); + } + + return await base.SavingChangesAsync(eventData, result, cancellationToken); + } +} diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/Sqlite/AbpEntityFrameworkCoreSqliteModule.cs b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/Sqlite/AbpEntityFrameworkCoreSqliteModule.cs index 52415d73b1..6b767ceee4 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/Sqlite/AbpEntityFrameworkCoreSqliteModule.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore.Sqlite/Volo/Abp/EntityFrameworkCore/Sqlite/AbpEntityFrameworkCoreSqliteModule.cs @@ -1,4 +1,6 @@ +using Microsoft.Extensions.DependencyInjection; using Volo.Abp.EntityFrameworkCore.GlobalFilters; +using Volo.Abp.EntityFrameworkCore.Interceptors; using Volo.Abp.Modularity; namespace Volo.Abp.EntityFrameworkCore.Sqlite; @@ -8,11 +10,31 @@ namespace Volo.Abp.EntityFrameworkCore.Sqlite; )] public class AbpEntityFrameworkCoreSqliteModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(options => + { + options.BusyTimeout = 5000; + }); + } + public override void ConfigureServices(ServiceConfigurationContext context) { Configure(options => { options.UseDbFunction = true; }); + + var sqliteOptions = context.Services.ExecutePreConfiguredActions(); + if (sqliteOptions.BusyTimeout.HasValue) + { + Configure(options => + { + options.ConfigureDefaultOnConfiguring((dbContext, dbContextOptionsBuilder) => + { + dbContextOptionsBuilder.AddInterceptors(new SqliteBusyTimeoutSaveChangesInterceptor(sqliteOptions.BusyTimeout.Value)); + }, overrideExisting: false); + }); + } } } diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs index 4b79bf017c..9243d4680e 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs @@ -117,6 +117,17 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, { optionsBuilder.ConfigureWarnings(c => c.Ignore(RelationalEventId.PendingModelChangesWarning)); base.OnConfiguring(optionsBuilder); + + if (LazyServiceProvider == null || Options == null) + { + return; + } + + Options.Value.DefaultOnConfiguringAction?.Invoke(this, optionsBuilder); + foreach (var onConfiguringAction in Options.Value.OnConfiguringActions.GetOrDefault(typeof(TDbContext)) ?? []) + { + onConfiguringAction.As>().Invoke(this, optionsBuilder); + } } protected override void OnModelCreating(ModelBuilder modelBuilder) @@ -804,7 +815,7 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, modelBuilder, mutableEntityType ); - + entityTypeBuilder.ConfigureByConvention(); ConfigureGlobalFilters(modelBuilder, mutableEntityType, entityTypeBuilder); @@ -821,7 +832,7 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, protected virtual void ConfigureGlobalFilters( ModelBuilder modelBuilder, - IMutableEntityType mutableEntityType, + IMutableEntityType mutableEntityType, EntityTypeBuilder entityTypeBuilder) where TEntity : class { @@ -852,7 +863,7 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, { return; } - + foreach (var property in mutableEntityType.GetProperties(). Where(property => property.PropertyInfo != null && @@ -864,7 +875,7 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, modelBuilder, mutableEntityType ); - + entityTypeBuilder .Property(property.Name) .HasConversion(property.ClrType == typeof(DateTime) @@ -874,7 +885,7 @@ public abstract class AbpDbContext : DbContext, IAbpEfCoreDbContext, } protected virtual void ConfigureValueGenerated( - ModelBuilder modelBuilder, + ModelBuilder modelBuilder, IMutableEntityType mutableEntityType) where TEntity : class { diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptions.cs index acde97340b..75cf409608 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptions.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContextOptions.cs @@ -27,8 +27,12 @@ public class AbpDbContextOptions internal Action? DefaultOnModelCreatingAction { get; set; } + internal Action? DefaultOnConfiguringAction { get; set; } + internal Dictionary> OnModelCreatingActions { get; } + internal Dictionary> OnConfiguringActions { get; } + public AbpDbContextOptions() { DefaultPreConfigureActions = new List>(); @@ -37,6 +41,7 @@ public class AbpDbContextOptions DbContextReplacements = new Dictionary(); ConventionActions = new Dictionary>(); OnModelCreatingActions = new Dictionary>(); + OnConfiguringActions = new Dictionary>(); } public void PreConfigure([NotNull] Action action) @@ -53,11 +58,18 @@ public class AbpDbContextOptions DefaultConfigureAction = action; } - public void ConfigureDefaultConvention([NotNull] Action action) + public void ConfigureDefaultConvention([NotNull] Action action, bool overrideExisting = false) { Check.NotNull(action, nameof(action)); - DefaultConventionAction = action; + if (overrideExisting) + { + DefaultConventionAction = action; + } + else + { + DefaultConventionAction += action; + } } public void ConfigureConventions([NotNull] Action action) @@ -78,11 +90,32 @@ public class AbpDbContextOptions actions.Add(action); } - public void ConfigureDefaultOnModelCreating([NotNull] Action action) + public void ConfigureDefaultOnModelCreating([NotNull] Action action, bool overrideExisting = false) { Check.NotNull(action, nameof(action)); - DefaultOnModelCreatingAction = action; + if (overrideExisting) + { + DefaultOnModelCreatingAction = action; + } + else + { + DefaultOnModelCreatingAction += action; + } + } + + public void ConfigureDefaultOnConfiguring([NotNull] Action action, bool overrideExisting = false) + { + Check.NotNull(action, nameof(action)); + + if (overrideExisting) + { + DefaultOnConfiguringAction = action; + } + else + { + DefaultOnConfiguringAction += action; + } } public void ConfigureOnModelCreating([NotNull] Action action) @@ -103,6 +136,24 @@ public class AbpDbContextOptions actions.Add(action); } + public void ConfigureOnConfiguring([NotNull] Action action) + where TDbContext : AbpDbContext + { + Check.NotNull(action, nameof(action)); + + var actions = OnConfiguringActions.GetOrDefault(typeof(TDbContext)); + if (actions == null) + { + OnConfiguringActions[typeof(TDbContext)] = new List + { + new Action((dbContext, builder) => action((TDbContext)dbContext, builder)) + }; + return; + } + + actions.Add(action); + } + public bool IsConfiguredDefault() { return DefaultConfigureAction != null; diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index 4bdf6a2782..a3376d9429 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -168,7 +168,7 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear()); } - protected async override Task PublishToEventBusAsync(Type eventType, object eventData) + protected override async Task PublishToEventBusAsync(Type eventType, object eventData) { var headers = new Headers { @@ -193,7 +193,7 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen unitOfWork.AddOrReplaceDistributedEvent(eventRecord); } - public async override Task PublishFromOutboxAsync( + public override async Task PublishFromOutboxAsync( OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) { @@ -206,13 +206,18 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen headers.Add(EventBusConsts.CorrelationIdHeaderName, System.Text.Encoding.UTF8.GetBytes(outgoingEvent.GetCorrelationId()!)); } - await PublishAsync( + var result = await PublishAsync( AbpKafkaEventBusOptions.TopicName, outgoingEvent.EventName, outgoingEvent.EventData, headers ); + if (result.Status != PersistenceStatus.Persisted) + { + throw new AbpException($"Failed to publish event '{outgoingEvent.EventName}' to topic '{AbpKafkaEventBusOptions.TopicName}'. Status: {result.Status}"); + } + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { await TriggerDistributedEventSentAsync(new DistributedEventSent() @@ -224,7 +229,7 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen } } - public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + public override async Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) { var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); var outgoingEventArray = outgoingEvents.ToArray(); @@ -242,7 +247,7 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen headers.Add(EventBusConsts.CorrelationIdHeaderName, System.Text.Encoding.UTF8.GetBytes(outgoingEvent.GetCorrelationId()!)); } - producer.Produce( + var result = await producer.ProduceAsync( AbpKafkaEventBusOptions.TopicName, new Message { @@ -251,6 +256,11 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen Headers = headers }); + if (result.Status != PersistenceStatus.Persisted) + { + throw new AbpException($"Failed to publish event '{outgoingEvent.EventName}' to topic '{AbpKafkaEventBusOptions.TopicName}'. Status: {result.Status}"); + } + using (CorrelationIdProvider.Change(outgoingEvent.GetCorrelationId())) { await TriggerDistributedEventSentAsync(new DistributedEventSent() @@ -263,7 +273,7 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen } } - public async override Task ProcessFromInboxAsync( + public override async Task ProcessFromInboxAsync( IncomingEventInfo incomingEvent, InboxConfig inboxConfig) { @@ -290,12 +300,16 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen return Serializer.Serialize(eventData); } - private Task PublishAsync(string topicName, Type eventType, object eventData, Headers headers) + private async Task PublishAsync(string topicName, Type eventType, object eventData, Headers headers) { var eventName = EventNameAttribute.GetNameOrDefault(eventType); var body = Serializer.Serialize(eventData); - return PublishAsync(topicName, eventName, body, headers); + var result = await PublishAsync(topicName, eventName, body, headers); + if (result.Status != PersistenceStatus.Persisted) + { + throw new AbpException($"Failed to publish event '{eventName}' to topic '{topicName}'. Status: {result.Status}"); + } } private Task> PublishAsync( diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs index 23b9b71a57..a22f5970a2 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs @@ -17,7 +17,7 @@ public class ProducerPool : IProducerPool, ISingletonDependency protected ConcurrentDictionary>> Producers { get; } protected TimeSpan TotalDisposeWaitDuration { get; set; } = TimeSpan.FromSeconds(10); - + protected TimeSpan DefaultTransactionsWaitDuration { get; set; } = TimeSpan.FromSeconds(30); public ILogger Logger { get; set; } @@ -41,8 +41,10 @@ public class ProducerPool : IProducerPool, ISingletonDependency { var producerConfig = new ProducerConfig(Options.Connections.GetOrDefault(connection).ToDictionary(k => k.Key, v => v.Value)); Options.ConfigureProducer?.Invoke(producerConfig); + producerConfig.Acks ??= Acks.All; + producerConfig.EnableIdempotence ??= true; return new ProducerBuilder(producerConfig).Build(); - + })).Value; } @@ -70,7 +72,7 @@ public class ProducerPool : IProducerPool, ISingletonDependency foreach (var producer in Producers.Values) { var poolItemDisposeStopwatch = Stopwatch.StartNew(); - + try { producer.Value.Dispose(); @@ -78,19 +80,19 @@ public class ProducerPool : IProducerPool, ISingletonDependency catch { } - + poolItemDisposeStopwatch.Stop(); - + remainingWaitDuration = remainingWaitDuration > poolItemDisposeStopwatch.Elapsed ? remainingWaitDuration.Subtract(poolItemDisposeStopwatch.Elapsed) : TimeSpan.Zero; } - + poolDisposeStopwatch.Stop(); - + Logger.LogInformation( $"Disposed Kafka Producer Pool ({Producers.Count} producers in {poolDisposeStopwatch.Elapsed.TotalMilliseconds:0.00} ms)."); - + if (poolDisposeStopwatch.Elapsed.TotalSeconds > 5.0) { Logger.LogWarning( diff --git a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantResolver.cs b/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantResolver.cs index 4351ef62bb..602540dd03 100644 --- a/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantResolver.cs +++ b/framework/src/Volo.Abp.MultiTenancy/Volo/Abp/MultiTenancy/TenantResolver.cs @@ -1,6 +1,8 @@ using System; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; @@ -8,25 +10,31 @@ namespace Volo.Abp.MultiTenancy; public class TenantResolver : ITenantResolver, ITransientDependency { - private readonly IServiceProvider _serviceProvider; - private readonly AbpTenantResolveOptions _options; + public ILogger Logger { get; set; } + + protected IServiceProvider ServiceProvider { get; } + protected AbpTenantResolveOptions Options { get; } public TenantResolver(IOptions options, IServiceProvider serviceProvider) { - _serviceProvider = serviceProvider; - _options = options.Value; + Logger = NullLogger.Instance; + + ServiceProvider = serviceProvider; + Options = options.Value; } public virtual async Task ResolveTenantIdOrNameAsync() { var result = new TenantResolveResult(); - using (var serviceScope = _serviceProvider.CreateScope()) + Logger.LogDebug("Starting resolving tenant..."); + using (var serviceScope = ServiceProvider.CreateScope()) { var context = new TenantResolveContext(serviceScope.ServiceProvider); - foreach (var tenantResolver in _options.TenantResolvers) + foreach (var tenantResolver in Options.TenantResolvers) { + Logger.LogDebug("Trying to resolve tenant through '{TenantResolverName}'...", tenantResolver.Name); await tenantResolver.ResolveAsync(context); result.AppliedResolvers.Add(tenantResolver.Name); @@ -34,15 +42,21 @@ public class TenantResolver : ITenantResolver, ITransientDependency if (context.HasResolvedTenantOrHost()) { result.TenantIdOrName = context.TenantIdOrName; + Logger.LogDebug("Tenant resolved by '{TenantResolverName}' as '{TenantIdOrName}'.", tenantResolver.Name, result.TenantIdOrName ?? "Host"); break; } } } - if (result.TenantIdOrName.IsNullOrEmpty() && !string.IsNullOrWhiteSpace(_options.FallbackTenant)) + if (result.TenantIdOrName.IsNullOrEmpty() && !string.IsNullOrWhiteSpace(Options.FallbackTenant)) { - result.TenantIdOrName = _options.FallbackTenant; + result.TenantIdOrName = Options.FallbackTenant; result.AppliedResolvers.Add(TenantResolverNames.FallbackTenant); + Logger.LogDebug("No tenant resolved. Using fallback tenant as '{FallbackTenant}'.", result.TenantIdOrName); + } + else if (result.TenantIdOrName.IsNullOrEmpty()) + { + Logger.LogDebug("No tenant resolved."); } return result; diff --git a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs index 5f905ca062..a7b5c1e602 100644 --- a/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs +++ b/framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/AbpEntityFrameworkCoreTestModule.cs @@ -30,6 +30,7 @@ public class AbpEntityFrameworkCoreTestModule : AbpModule public override void PreConfigureServices(ServiceConfigurationContext context) { TestEntityExtensionConfigurator.Configure(); + PreConfigure(x => x.BusyTimeout = null); } public override void ConfigureServices(ServiceConfigurationContext context) diff --git a/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo/Abp/AuditLogging/EntityFrameworkCore/AbpAuditLoggingEntityFrameworkCoreTestModule.cs b/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo/Abp/AuditLogging/EntityFrameworkCore/AbpAuditLoggingEntityFrameworkCoreTestModule.cs index 42d246625e..9d73151632 100644 --- a/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo/Abp/AuditLogging/EntityFrameworkCore/AbpAuditLoggingEntityFrameworkCoreTestModule.cs +++ b/modules/audit-logging/test/Volo.Abp.AuditLogging.EntityFrameworkCore.Tests/Volo/Abp/AuditLogging/EntityFrameworkCore/AbpAuditLoggingEntityFrameworkCoreTestModule.cs @@ -16,6 +16,11 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore; )] public class AbpAuditLoggingEntityFrameworkCoreTestModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(x => x.BusyTimeout = null); + } + public override void ConfigureServices(ServiceConfigurationContext context) { var sqliteConnection = CreateDatabaseAndGetConnection(); diff --git a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo/Abp/BackgroundJobs/EntityFrameworkCore/AbpBackgroundJobsEntityFrameworkCoreTestModule.cs b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo/Abp/BackgroundJobs/EntityFrameworkCore/AbpBackgroundJobsEntityFrameworkCoreTestModule.cs index c19016d0d0..ece6052449 100644 --- a/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo/Abp/BackgroundJobs/EntityFrameworkCore/AbpBackgroundJobsEntityFrameworkCoreTestModule.cs +++ b/modules/background-jobs/test/Volo.Abp.BackgroundJobs.EntityFrameworkCore.Tests/Volo/Abp/BackgroundJobs/EntityFrameworkCore/AbpBackgroundJobsEntityFrameworkCoreTestModule.cs @@ -16,6 +16,11 @@ namespace Volo.Abp.BackgroundJobs.EntityFrameworkCore; )] public class AbpBackgroundJobsEntityFrameworkCoreTestModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(x => x.BusyTimeout = null); + } + public override void ConfigureServices(ServiceConfigurationContext context) { var sqliteConnection = CreateDatabaseAndGetConnection(); diff --git a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/BlobStoringDatabaseEntityFrameworkCoreTestModule.cs b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/BlobStoringDatabaseEntityFrameworkCoreTestModule.cs index 038ca5874d..beae51969a 100644 --- a/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/BlobStoringDatabaseEntityFrameworkCoreTestModule.cs +++ b/modules/blob-storing-database/test/Volo.Abp.BlobStoring.Database.EntityFrameworkCore.Tests/EntityFrameworkCore/BlobStoringDatabaseEntityFrameworkCoreTestModule.cs @@ -15,6 +15,11 @@ namespace Volo.Abp.BlobStoring.Database.EntityFrameworkCore; )] public class BlobStoringDatabaseEntityFrameworkCoreTestModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(x => x.BusyTimeout = null); + } + public override void ConfigureServices(ServiceConfigurationContext context) { var sqliteConnection = CreateDatabaseAndGetConnection(); diff --git a/modules/blogging/test/Volo.Blogging.EntityFrameworkCore.Tests/Volo/Blogging/EntityFrameworkCore/BloggingEntityFrameworkCoreTestModule.cs b/modules/blogging/test/Volo.Blogging.EntityFrameworkCore.Tests/Volo/Blogging/EntityFrameworkCore/BloggingEntityFrameworkCoreTestModule.cs index 498e8e7ab6..2d40880875 100644 --- a/modules/blogging/test/Volo.Blogging.EntityFrameworkCore.Tests/Volo/Blogging/EntityFrameworkCore/BloggingEntityFrameworkCoreTestModule.cs +++ b/modules/blogging/test/Volo.Blogging.EntityFrameworkCore.Tests/Volo/Blogging/EntityFrameworkCore/BloggingEntityFrameworkCoreTestModule.cs @@ -18,6 +18,11 @@ namespace Volo.Blogging.EntityFrameworkCore { private SqliteConnection _sqliteConnection; + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(x => x.BusyTimeout = null); + } + public override void ConfigureServices(ServiceConfigurationContext context) { _sqliteConnection = CreateDatabaseAndGetConnection(); diff --git a/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/EntityFrameworkCore/CmsKitEntityFrameworkCoreTestModule.cs b/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/EntityFrameworkCore/CmsKitEntityFrameworkCoreTestModule.cs index 3c28251536..659fa551b2 100644 --- a/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/EntityFrameworkCore/CmsKitEntityFrameworkCoreTestModule.cs +++ b/modules/cms-kit/test/Volo.CmsKit.EntityFrameworkCore.Tests/EntityFrameworkCore/CmsKitEntityFrameworkCoreTestModule.cs @@ -20,6 +20,7 @@ public class CmsKitEntityFrameworkCoreTestModule : AbpModule { public override void PreConfigureServices(ServiceConfigurationContext context) { + PreConfigure(x => x.BusyTimeout = null); context.Services.AddDataMigrationEnvironment(); } diff --git a/modules/docs/test/Volo.Docs.EntityFrameworkCore.Tests/Volo/Docs/EntityFrameworkCore/DocsEntityFrameworkCoreTestModule.cs b/modules/docs/test/Volo.Docs.EntityFrameworkCore.Tests/Volo/Docs/EntityFrameworkCore/DocsEntityFrameworkCoreTestModule.cs index fe3e32291f..b61b8368d6 100644 --- a/modules/docs/test/Volo.Docs.EntityFrameworkCore.Tests/Volo/Docs/EntityFrameworkCore/DocsEntityFrameworkCoreTestModule.cs +++ b/modules/docs/test/Volo.Docs.EntityFrameworkCore.Tests/Volo/Docs/EntityFrameworkCore/DocsEntityFrameworkCoreTestModule.cs @@ -15,6 +15,11 @@ namespace Volo.Docs.EntityFrameworkCore )] public class DocsEntityFrameworkCoreTestModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(x => x.BusyTimeout = null); + } + public override void ConfigureServices(ServiceConfigurationContext context) { var sqliteConnection = CreateDatabaseAndGetConnection(); diff --git a/modules/feature-management/test/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests/Volo/Abp/FeatureManagement/EntityFrameworkCore/AbpFeatureManagementEntityFrameworkCoreTestModule.cs b/modules/feature-management/test/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests/Volo/Abp/FeatureManagement/EntityFrameworkCore/AbpFeatureManagementEntityFrameworkCoreTestModule.cs index 242cf75233..c370e5f558 100644 --- a/modules/feature-management/test/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests/Volo/Abp/FeatureManagement/EntityFrameworkCore/AbpFeatureManagementEntityFrameworkCoreTestModule.cs +++ b/modules/feature-management/test/Volo.Abp.FeatureManagement.EntityFrameworkCore.Tests/Volo/Abp/FeatureManagement/EntityFrameworkCore/AbpFeatureManagementEntityFrameworkCoreTestModule.cs @@ -19,6 +19,11 @@ namespace Volo.Abp.FeatureManagement.EntityFrameworkCore; )] public class AbpFeatureManagementEntityFrameworkCoreTestModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(x => x.BusyTimeout = null); + } + public override void ConfigureServices(ServiceConfigurationContext context) { var sqliteConnection = CreateDatabaseAndGetConnection(); diff --git a/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreTestModule.cs b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreTestModule.cs index f0d1966bd0..6ee1dd34b7 100644 --- a/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreTestModule.cs +++ b/modules/identity/test/Volo.Abp.Identity.EntityFrameworkCore.Tests/Volo/Abp/Identity/EntityFrameworkCore/AbpIdentityEntityFrameworkCoreTestModule.cs @@ -18,6 +18,11 @@ namespace Volo.Abp.Identity.EntityFrameworkCore; )] public class AbpIdentityEntityFrameworkCoreTestModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(x => x.BusyTimeout = null); + } + public override void ConfigureServices(ServiceConfigurationContext context) { var sqliteConnection = CreateDatabaseAndGetConnection(); diff --git a/modules/openiddict/test/Volo.Abp.OpenIddict.EntityFrameworkCore.Tests/Volo/Abp/OpenIddict/EntityFrameworkCore/OpenIddictEntityFrameworkCoreTestModule.cs b/modules/openiddict/test/Volo.Abp.OpenIddict.EntityFrameworkCore.Tests/Volo/Abp/OpenIddict/EntityFrameworkCore/OpenIddictEntityFrameworkCoreTestModule.cs index c47ecc7996..d2286bacbb 100644 --- a/modules/openiddict/test/Volo.Abp.OpenIddict.EntityFrameworkCore.Tests/Volo/Abp/OpenIddict/EntityFrameworkCore/OpenIddictEntityFrameworkCoreTestModule.cs +++ b/modules/openiddict/test/Volo.Abp.OpenIddict.EntityFrameworkCore.Tests/Volo/Abp/OpenIddict/EntityFrameworkCore/OpenIddictEntityFrameworkCoreTestModule.cs @@ -20,6 +20,11 @@ namespace Volo.Abp.OpenIddict.EntityFrameworkCore; )] public class OpenIddictEntityFrameworkCoreTestModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(x => x.BusyTimeout = null); + } + public override void ConfigureServices(ServiceConfigurationContext context) { var sqliteConnection = CreateDatabaseAndGetConnection(); diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests.csproj b/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests.csproj index f99bf791dd..45c157641b 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests.csproj +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests.csproj @@ -15,10 +15,10 @@ + - diff --git a/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementEntityFrameworkCoreTestModule.cs b/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementEntityFrameworkCoreTestModule.cs index a73e5afb86..1e483fe1aa 100644 --- a/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementEntityFrameworkCoreTestModule.cs +++ b/modules/permission-management/test/Volo.Abp.PermissionManagement.EntityFrameworkCore.Tests/Volo/Abp/PermissionManagement/EntityFrameworkCore/AbpPermissionManagementEntityFrameworkCoreTestModule.cs @@ -1,36 +1,62 @@ -using System; -using System.Threading.Tasks; +using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage; using Microsoft.Extensions.DependencyInjection; using Volo.Abp.EntityFrameworkCore; +using Volo.Abp.EntityFrameworkCore.Sqlite; using Volo.Abp.Modularity; using Volo.Abp.Threading; using Volo.Abp.Uow; +using Microsoft.Data.Sqlite; namespace Volo.Abp.PermissionManagement.EntityFrameworkCore; [DependsOn( typeof(AbpPermissionManagementEntityFrameworkCoreModule), - typeof(AbpPermissionManagementTestBaseModule))] + typeof(AbpPermissionManagementTestBaseModule), + typeof(AbpEntityFrameworkCoreSqliteModule) +)] public class AbpPermissionManagementEntityFrameworkCoreTestModule : AbpModule { - public override void ConfigureServices(ServiceConfigurationContext context) + public override void PreConfigureServices(ServiceConfigurationContext context) { - context.Services.AddEntityFrameworkInMemoryDatabase(); + PreConfigure(x => x.BusyTimeout = null); + } - var databaseName = Guid.NewGuid().ToString(); + public override void ConfigureServices(ServiceConfigurationContext context) + { + var sqliteConnection = CreateDatabaseAndGetConnection(); Configure(options => { options.Configure(abpDbContextConfigurationContext => { - abpDbContextConfigurationContext.DbContextOptions.UseInMemoryDatabase(databaseName); + abpDbContextConfigurationContext.DbContextOptions.UseSqlite(sqliteConnection); }); }); + Configure(options => + { + options.TransactionBehavior = UnitOfWorkTransactionBehavior.Disabled; //EF in-memory database does not support transactions + }); + context.Services.AddAlwaysDisableUnitOfWorkTransaction(); } + private static SqliteConnection CreateDatabaseAndGetConnection() + { + var connection = new AbpUnitTestSqliteConnection("Data Source=:memory:"); + connection.Open(); + + new PermissionManagementDbContext( + new DbContextOptionsBuilder().UseSqlite(connection).Options + ).GetService().CreateTables(); + + return connection; + } + + public override void OnApplicationInitialization(ApplicationInitializationContext context) { var task = context.ServiceProvider.GetRequiredService().GetInitializeDynamicPermissionsTask(); diff --git a/modules/setting-management/src/Volo.Abp.SettingManagement.Installer/AngularInstallationInfo.json b/modules/setting-management/src/Volo.Abp.SettingManagement.Installer/AngularInstallationInfo.json index ae67780dee..59abcbddf8 100644 --- a/modules/setting-management/src/Volo.Abp.SettingManagement.Installer/AngularInstallationInfo.json +++ b/modules/setting-management/src/Volo.Abp.SettingManagement.Installer/AngularInstallationInfo.json @@ -2,6 +2,7 @@ "packages":[ { "name": "@abp/ng.setting-management", + "keepPackageInPackageJson": true, "appRoutingModuleConfiguration":{ "routes":[ "{ path: 'setting-management', loadChildren: () => import('@abp/ng.setting-management').then(c => c.createRoutes()),}" diff --git a/modules/setting-management/test/Volo.Abp.SettingManagement.EntityFrameworkCore.Tests/Volo/Abp/SettingManagement/EntityFrameworkCore/AbpSettingManagementEntityFrameworkCoreTestModule.cs b/modules/setting-management/test/Volo.Abp.SettingManagement.EntityFrameworkCore.Tests/Volo/Abp/SettingManagement/EntityFrameworkCore/AbpSettingManagementEntityFrameworkCoreTestModule.cs index 58df348af7..c5249ce8f0 100644 --- a/modules/setting-management/test/Volo.Abp.SettingManagement.EntityFrameworkCore.Tests/Volo/Abp/SettingManagement/EntityFrameworkCore/AbpSettingManagementEntityFrameworkCoreTestModule.cs +++ b/modules/setting-management/test/Volo.Abp.SettingManagement.EntityFrameworkCore.Tests/Volo/Abp/SettingManagement/EntityFrameworkCore/AbpSettingManagementEntityFrameworkCoreTestModule.cs @@ -17,6 +17,11 @@ namespace Volo.Abp.SettingManagement.EntityFrameworkCore; )] public class AbpSettingManagementEntityFrameworkCoreTestModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(x => x.BusyTimeout = null); + } + public override void ConfigureServices(ServiceConfigurationContext context) { var sqliteConnection = CreateDatabaseAndGetConnection(); diff --git a/modules/tenant-management/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo/Abp/TenantManagement/EntityFrameworkCore/AbpTenantManagementEntityFrameworkCoreTestModule.cs b/modules/tenant-management/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo/Abp/TenantManagement/EntityFrameworkCore/AbpTenantManagementEntityFrameworkCoreTestModule.cs index 336fbb228f..4b058822a9 100644 --- a/modules/tenant-management/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo/Abp/TenantManagement/EntityFrameworkCore/AbpTenantManagementEntityFrameworkCoreTestModule.cs +++ b/modules/tenant-management/test/Volo.Abp.TenantManagement.EntityFrameworkCore.Tests/Volo/Abp/TenantManagement/EntityFrameworkCore/AbpTenantManagementEntityFrameworkCoreTestModule.cs @@ -17,6 +17,11 @@ namespace Volo.Abp.TenantManagement.EntityFrameworkCore; )] public class AbpTenantManagementEntityFrameworkCoreTestModule : AbpModule { + public override void PreConfigureServices(ServiceConfigurationContext context) + { + PreConfigure(x => x.BusyTimeout = null); + } + public override void ConfigureServices(ServiceConfigurationContext context) { var sqliteConnection = CreateDatabaseAndGetConnection(); diff --git a/source-code/Volo.Abp.BasicTheme.SourceCode/Volo.Abp.BasicTheme.SourceCode.zip b/source-code/Volo.Abp.BasicTheme.SourceCode/Volo.Abp.BasicTheme.SourceCode.zip index 8d59ce6a20..6bb97f976a 100644 Binary files a/source-code/Volo.Abp.BasicTheme.SourceCode/Volo.Abp.BasicTheme.SourceCode.zip and b/source-code/Volo.Abp.BasicTheme.SourceCode/Volo.Abp.BasicTheme.SourceCode.zip differ diff --git a/source-code/Volo.ClientSimulation.SourceCode/Volo.ClientSimulation.SourceCode.zip b/source-code/Volo.ClientSimulation.SourceCode/Volo.ClientSimulation.SourceCode.zip index 3852bd5bb6..3514451835 100644 Binary files a/source-code/Volo.ClientSimulation.SourceCode/Volo.ClientSimulation.SourceCode.zip and b/source-code/Volo.ClientSimulation.SourceCode/Volo.ClientSimulation.SourceCode.zip differ