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.
+
+
+
+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 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