Move AI EF Core migrations to provider projects

This commit is contained in:
Sipke Schoorstra 2026-06-08 15:22:59 +02:00
parent 92f451e655
commit 5c0d8b0f4e
No known key found for this signature in database
GPG key ID: 5C10502B28A4268F
55 changed files with 3653 additions and 501 deletions

103
Elsa.sln
View file

@ -427,6 +427,16 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "keyvalues", "keyvalues", "{
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dashboard", "dashboard", "{DD8D1712-B42C-4BF2-99AF-3F092E508C62}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.AI.Persistence.EFCore.Sqlite", "src\modules\Elsa.AI.Persistence.EFCore.Sqlite\Elsa.AI.Persistence.EFCore.Sqlite.csproj", "{4A57961A-ED52-471F-BA21-EAEE9100E485}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.AI.Persistence.EFCore.SqlServer", "src\modules\Elsa.AI.Persistence.EFCore.SqlServer\Elsa.AI.Persistence.EFCore.SqlServer.csproj", "{6BE15A6D-F543-4084-BF6E-F00FD71F60F3}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.AI.Persistence.EFCore.PostgreSql", "src\modules\Elsa.AI.Persistence.EFCore.PostgreSql\Elsa.AI.Persistence.EFCore.PostgreSql.csproj", "{32F2EE33-8190-41D7-BD12-45AA02BEB930}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.AI.Persistence.EFCore.MySql", "src\modules\Elsa.AI.Persistence.EFCore.MySql\Elsa.AI.Persistence.EFCore.MySql.csproj", "{2D51FFE0-F114-48E1-BF58-FCED0FD1944F}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Elsa.AI.Persistence.EFCore.Oracle", "src\modules\Elsa.AI.Persistence.EFCore.Oracle\Elsa.AI.Persistence.EFCore.Oracle.csproj", "{743C1835-E41E-4E5C-9D12-585BAEE59EEE}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -1887,6 +1897,66 @@ Global
{DDEA0B6A-A615-4CF8-8B23-F93E390454B2}.Release|x64.Build.0 = Release|Any CPU
{DDEA0B6A-A615-4CF8-8B23-F93E390454B2}.Release|x86.ActiveCfg = Release|Any CPU
{DDEA0B6A-A615-4CF8-8B23-F93E390454B2}.Release|x86.Build.0 = Release|Any CPU
{4A57961A-ED52-471F-BA21-EAEE9100E485}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{4A57961A-ED52-471F-BA21-EAEE9100E485}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4A57961A-ED52-471F-BA21-EAEE9100E485}.Debug|x64.ActiveCfg = Debug|Any CPU
{4A57961A-ED52-471F-BA21-EAEE9100E485}.Debug|x64.Build.0 = Debug|Any CPU
{4A57961A-ED52-471F-BA21-EAEE9100E485}.Debug|x86.ActiveCfg = Debug|Any CPU
{4A57961A-ED52-471F-BA21-EAEE9100E485}.Debug|x86.Build.0 = Debug|Any CPU
{4A57961A-ED52-471F-BA21-EAEE9100E485}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4A57961A-ED52-471F-BA21-EAEE9100E485}.Release|Any CPU.Build.0 = Release|Any CPU
{4A57961A-ED52-471F-BA21-EAEE9100E485}.Release|x64.ActiveCfg = Release|Any CPU
{4A57961A-ED52-471F-BA21-EAEE9100E485}.Release|x64.Build.0 = Release|Any CPU
{4A57961A-ED52-471F-BA21-EAEE9100E485}.Release|x86.ActiveCfg = Release|Any CPU
{4A57961A-ED52-471F-BA21-EAEE9100E485}.Release|x86.Build.0 = Release|Any CPU
{6BE15A6D-F543-4084-BF6E-F00FD71F60F3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6BE15A6D-F543-4084-BF6E-F00FD71F60F3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6BE15A6D-F543-4084-BF6E-F00FD71F60F3}.Debug|x64.ActiveCfg = Debug|Any CPU
{6BE15A6D-F543-4084-BF6E-F00FD71F60F3}.Debug|x64.Build.0 = Debug|Any CPU
{6BE15A6D-F543-4084-BF6E-F00FD71F60F3}.Debug|x86.ActiveCfg = Debug|Any CPU
{6BE15A6D-F543-4084-BF6E-F00FD71F60F3}.Debug|x86.Build.0 = Debug|Any CPU
{6BE15A6D-F543-4084-BF6E-F00FD71F60F3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6BE15A6D-F543-4084-BF6E-F00FD71F60F3}.Release|Any CPU.Build.0 = Release|Any CPU
{6BE15A6D-F543-4084-BF6E-F00FD71F60F3}.Release|x64.ActiveCfg = Release|Any CPU
{6BE15A6D-F543-4084-BF6E-F00FD71F60F3}.Release|x64.Build.0 = Release|Any CPU
{6BE15A6D-F543-4084-BF6E-F00FD71F60F3}.Release|x86.ActiveCfg = Release|Any CPU
{6BE15A6D-F543-4084-BF6E-F00FD71F60F3}.Release|x86.Build.0 = Release|Any CPU
{32F2EE33-8190-41D7-BD12-45AA02BEB930}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{32F2EE33-8190-41D7-BD12-45AA02BEB930}.Debug|Any CPU.Build.0 = Debug|Any CPU
{32F2EE33-8190-41D7-BD12-45AA02BEB930}.Debug|x64.ActiveCfg = Debug|Any CPU
{32F2EE33-8190-41D7-BD12-45AA02BEB930}.Debug|x64.Build.0 = Debug|Any CPU
{32F2EE33-8190-41D7-BD12-45AA02BEB930}.Debug|x86.ActiveCfg = Debug|Any CPU
{32F2EE33-8190-41D7-BD12-45AA02BEB930}.Debug|x86.Build.0 = Debug|Any CPU
{32F2EE33-8190-41D7-BD12-45AA02BEB930}.Release|Any CPU.ActiveCfg = Release|Any CPU
{32F2EE33-8190-41D7-BD12-45AA02BEB930}.Release|Any CPU.Build.0 = Release|Any CPU
{32F2EE33-8190-41D7-BD12-45AA02BEB930}.Release|x64.ActiveCfg = Release|Any CPU
{32F2EE33-8190-41D7-BD12-45AA02BEB930}.Release|x64.Build.0 = Release|Any CPU
{32F2EE33-8190-41D7-BD12-45AA02BEB930}.Release|x86.ActiveCfg = Release|Any CPU
{32F2EE33-8190-41D7-BD12-45AA02BEB930}.Release|x86.Build.0 = Release|Any CPU
{2D51FFE0-F114-48E1-BF58-FCED0FD1944F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2D51FFE0-F114-48E1-BF58-FCED0FD1944F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2D51FFE0-F114-48E1-BF58-FCED0FD1944F}.Debug|x64.ActiveCfg = Debug|Any CPU
{2D51FFE0-F114-48E1-BF58-FCED0FD1944F}.Debug|x64.Build.0 = Debug|Any CPU
{2D51FFE0-F114-48E1-BF58-FCED0FD1944F}.Debug|x86.ActiveCfg = Debug|Any CPU
{2D51FFE0-F114-48E1-BF58-FCED0FD1944F}.Debug|x86.Build.0 = Debug|Any CPU
{2D51FFE0-F114-48E1-BF58-FCED0FD1944F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2D51FFE0-F114-48E1-BF58-FCED0FD1944F}.Release|Any CPU.Build.0 = Release|Any CPU
{2D51FFE0-F114-48E1-BF58-FCED0FD1944F}.Release|x64.ActiveCfg = Release|Any CPU
{2D51FFE0-F114-48E1-BF58-FCED0FD1944F}.Release|x64.Build.0 = Release|Any CPU
{2D51FFE0-F114-48E1-BF58-FCED0FD1944F}.Release|x86.ActiveCfg = Release|Any CPU
{2D51FFE0-F114-48E1-BF58-FCED0FD1944F}.Release|x86.Build.0 = Release|Any CPU
{743C1835-E41E-4E5C-9D12-585BAEE59EEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{743C1835-E41E-4E5C-9D12-585BAEE59EEE}.Debug|Any CPU.Build.0 = Debug|Any CPU
{743C1835-E41E-4E5C-9D12-585BAEE59EEE}.Debug|x64.ActiveCfg = Debug|Any CPU
{743C1835-E41E-4E5C-9D12-585BAEE59EEE}.Debug|x64.Build.0 = Debug|Any CPU
{743C1835-E41E-4E5C-9D12-585BAEE59EEE}.Debug|x86.ActiveCfg = Debug|Any CPU
{743C1835-E41E-4E5C-9D12-585BAEE59EEE}.Debug|x86.Build.0 = Debug|Any CPU
{743C1835-E41E-4E5C-9D12-585BAEE59EEE}.Release|Any CPU.ActiveCfg = Release|Any CPU
{743C1835-E41E-4E5C-9D12-585BAEE59EEE}.Release|Any CPU.Build.0 = Release|Any CPU
{743C1835-E41E-4E5C-9D12-585BAEE59EEE}.Release|x64.ActiveCfg = Release|Any CPU
{743C1835-E41E-4E5C-9D12-585BAEE59EEE}.Release|x64.Build.0 = Release|Any CPU
{743C1835-E41E-4E5C-9D12-585BAEE59EEE}.Release|x86.ActiveCfg = Release|Any CPU
{743C1835-E41E-4E5C-9D12-585BAEE59EEE}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1917,6 +1987,7 @@ Global
{29638A67-E79F-44FE-AC05-DA499EBA929E} = {2F3E1026-5054-4E1F-899B-F1A7F70F9912}
{A516931E-EDBB-4FC3-BB94-1BB824D5BC61} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{BBCE36D1-6767-4ED1-B3E8-84D2567A962A} = {A516931E-EDBB-4FC3-BB94-1BB824D5BC61}
{73BBB1E3-AEEC-44E8-83D7-6DD9CD687FE3} = {9323DC40-29B4-4438-AAD7-C4D4069C072B}
{B0946844-DE0E-4C59-9391-C0EB649DBF4E} = {18453B51-25EB-4317-A4B3-B10518252E92}
{34BC9836-681D-43A5-BC39-E4FEE17FC528} = {08B41FFA-CEE3-46A7-B5C0-3EB65D37A16C}
{CBB515F3-A0EF-43B5-A907-FD4E652DD66E} = {90031D64-CA0F-46D0-9AF4-8DC023A5FFCD}
@ -2020,21 +2091,12 @@ Global
{E4B1546A-3F35-4B5C-815C-07E58B00163F} = {D3E5E9EF-DE26-41BF-A239-3241B5B5FD89}
{E18931B2-82EE-4398-B38A-7B8B449957CD} = {D3E5E9EF-DE26-41BF-A239-3241B5B5FD89}
{729FD075-2863-48AB-A3C0-A4F7A9277263} = {D3E5E9EF-DE26-41BF-A239-3241B5B5FD89}
{4FD4C59B-2804-4F6B-AD38-2562CC01C510} = {DD8D1712-B42C-4BF2-99AF-3F092E508C62}
{157EDBA7-B04F-4EA0-8377-434D1065ACF6} = {18453B51-25EB-4317-A4B3-B10518252E92}
{27300F17-F959-4D4A-885F-FDDBE81456C1} = {DD8D1712-B42C-4BF2-99AF-3F092E508C62}
{05AC1253-922A-4BC4-93D0-765752026B29} = {B08B4E00-C2AB-48F3-8389-449F42AEF179}
{D1212632-20D4-4115-9E8F-05EEEC4604BE} = {78FD90A4-90A5-445F-97F2-74BA835AFA5D}
{215555CD-3B4A-46E7-ACE0-B4ADE1F0FE62} = {78FD90A4-90A5-445F-97F2-74BA835AFA5D}
{418472CD-1215-4962-9024-F26E9863B5A4} = {18453B51-25EB-4317-A4B3-B10518252E92}
{C597B963-A80F-4093-B3CC-9060854D9D38} = {18453B51-25EB-4317-A4B3-B10518252E92}
{E087B3BD-D579-422B-A29F-965D33E2C769} = {18453B51-25EB-4317-A4B3-B10518252E92}
{1C70A220-E69F-434B-966B-9C53A35B072B} = {18453B51-25EB-4317-A4B3-B10518252E92}
{9B74CCC3-9634-4A09-A270-CE1101D96824} = {18453B51-25EB-4317-A4B3-B10518252E92}
{DDEA0B6A-A615-4CF8-8B23-F93E390454B2} = {1B8D5897-902E-4632-8698-E89CAF3DDF54}
{F52DF980-C8F7-49D5-A8A1-249B44223600} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{E8D76C93-4FE6-4B97-BC16-C8490CF3C8CC} = {F52DF980-C8F7-49D5-A8A1-249B44223600}
{C9E1429E-BC22-4ABC-820D-08709793ECE7} = {F52DF980-C8F7-49D5-A8A1-249B44223600}
{24CE63F9-29D1-4243-9144-BB2C1590FCC0} = {F52DF980-C8F7-49D5-A8A1-249B44223600}
{D3F07276-CD16-46BD-AEB1-8C7D6D7910C5} = {F52DF980-C8F7-49D5-A8A1-249B44223600}
{A5BE660B-BED0-4D72-BC60-2E6EB42061C0} = {3D0A6C71-4B96-411B-80DB-DDFAFF77C748}
{86A93965-54B1-4983-9A87-6E3ADB584BED} = {3D0A6C71-4B96-411B-80DB-DDFAFF77C748}
{E1F2B414-D9E4-4789-8138-3F9DA076FFB6} = {3D0A6C71-4B96-411B-80DB-DDFAFF77C748}
@ -2044,11 +2106,24 @@ Global
{6A17BD0C-8643-4684-BC3F-9E378A467605} = {3D0A6C71-4B96-411B-80DB-DDFAFF77C748}
{2248A649-6CA4-4A1A-8BC3-34257238B560} = {3D0A6C71-4B96-411B-80DB-DDFAFF77C748}
{807C6ACA-1C4B-4AC4-A60C-1FE6B9AB38AC} = {D3E5E9EF-DE26-41BF-A239-3241B5B5FD89}
{418472CD-1215-4962-9024-F26E9863B5A4} = {18453B51-25EB-4317-A4B3-B10518252E92}
{E8D76C93-4FE6-4B97-BC16-C8490CF3C8CC} = {F52DF980-C8F7-49D5-A8A1-249B44223600}
{C9E1429E-BC22-4ABC-820D-08709793ECE7} = {F52DF980-C8F7-49D5-A8A1-249B44223600}
{24CE63F9-29D1-4243-9144-BB2C1590FCC0} = {F52DF980-C8F7-49D5-A8A1-249B44223600}
{D3F07276-CD16-46BD-AEB1-8C7D6D7910C5} = {F52DF980-C8F7-49D5-A8A1-249B44223600}
{C597B963-A80F-4093-B3CC-9060854D9D38} = {18453B51-25EB-4317-A4B3-B10518252E92}
{E087B3BD-D579-422B-A29F-965D33E2C769} = {18453B51-25EB-4317-A4B3-B10518252E92}
{1C70A220-E69F-434B-966B-9C53A35B072B} = {18453B51-25EB-4317-A4B3-B10518252E92}
{9B74CCC3-9634-4A09-A270-CE1101D96824} = {18453B51-25EB-4317-A4B3-B10518252E92}
{DDEA0B6A-A615-4CF8-8B23-F93E390454B2} = {1B8D5897-902E-4632-8698-E89CAF3DDF54}
{F52DF980-C8F7-49D5-A8A1-249B44223600} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{9323DC40-29B4-4438-AAD7-C4D4069C072B} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{73BBB1E3-AEEC-44E8-83D7-6DD9CD687FE3} = {9323DC40-29B4-4438-AAD7-C4D4069C072B}
{DD8D1712-B42C-4BF2-99AF-3F092E508C62} = {5BA4A8FA-F7F4-45B3-AEC8-8886D35AAC79}
{27300F17-F959-4D4A-885F-FDDBE81456C1} = {DD8D1712-B42C-4BF2-99AF-3F092E508C62}
{4FD4C59B-2804-4F6B-AD38-2562CC01C510} = {DD8D1712-B42C-4BF2-99AF-3F092E508C62}
{4A57961A-ED52-471F-BA21-EAEE9100E485} = {F52DF980-C8F7-49D5-A8A1-249B44223600}
{6BE15A6D-F543-4084-BF6E-F00FD71F60F3} = {F52DF980-C8F7-49D5-A8A1-249B44223600}
{32F2EE33-8190-41D7-BD12-45AA02BEB930} = {F52DF980-C8F7-49D5-A8A1-249B44223600}
{2D51FFE0-F114-48E1-BF58-FCED0FD1944F} = {F52DF980-C8F7-49D5-A8A1-249B44223600}
{743C1835-E41E-4E5C-9D12-585BAEE59EEE} = {F52DF980-C8F7-49D5-A8A1-249B44223600}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {D4B5CEAA-7D70-4FCB-A68E-B03FBE5E0E5E}

View file

@ -0,0 +1,24 @@
using Elsa.Persistence.EFCore.Abstractions;
using Elsa.Persistence.EFCore.Extensions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace Elsa.AI.Persistence.EFCore.MySql;
[UsedImplicitly]
public class AIDbContextFactory : MySqlAIDesignTimeDbContextFactory<AIDbContext>;
public class MySqlAIDesignTimeDbContextFactory<TDbContext> : DesignTimeDbContextFactoryBase<TDbContext> where TDbContext : DbContext
{
protected override void ConfigureServices(IServiceCollection services)
{
services.AddMySqlEntityModelCreatingHandlers();
}
protected override void ConfigureBuilder(DbContextOptionsBuilder<TDbContext> builder, string connectionString)
{
builder.UseElsaMySql(GetType().Assembly, connectionString, serverVersion: ServerVersion.Parse("9.0.0"));
}
}

View file

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<!-- Pomelo targets EF Core up to 9.x, so exclude MySQL provider for net10.0. -->
<TargetFrameworks>net8.0;net9.0</TargetFrameworks>
<Description>
Provides MySQL EF Core migrations for Elsa AI persistence.
</Description>
<PackageTags>elsa module ai persistence efcore mysql</PackageTags>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" PrivateAssets="all" />
<ProjectReference Include="..\Elsa.Persistence.EFCore.MySql\Elsa.Persistence.EFCore.MySql.csproj" />
<ProjectReference Include="..\Elsa.AI.Host\Elsa.AI.Host.csproj" />
<ProjectReference Include="..\Elsa.AI.Persistence.EFCore\Elsa.AI.Persistence.EFCore.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,30 @@
using System.Reflection;
using Elsa.AI.Persistence.EFCore.Features;
using Elsa.Persistence.EFCore;
using Elsa.Persistence.EFCore.Extensions;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace Elsa.AI.Persistence.EFCore.MySql.Extensions;
public static class MySqlAIPersistenceFeatureExtensions
{
private static Assembly Assembly => typeof(MySqlAIPersistenceFeatureExtensions).Assembly;
public static AIPersistenceFeature UseMySql(
this AIPersistenceFeature feature,
string connectionString,
ElsaDbContextOptions? options = null,
Action<MySqlDbContextOptionsBuilder>? configure = null)
{
return feature.UseMySql(_ => connectionString, options, configure);
}
public static AIPersistenceFeature UseMySql(
this AIPersistenceFeature feature,
Func<IServiceProvider, string> connectionStringFunc,
ElsaDbContextOptions? options = null,
Action<MySqlDbContextOptionsBuilder>? configure = null)
{
return feature.UseMySql(Assembly, connectionStringFunc, options, configure);
}
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

View file

@ -0,0 +1,208 @@
// <auto-generated />
using System;
using Elsa.AI.Persistence.EFCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Elsa.AI.Persistence.EFCore.MySql.Migrations.AI
{
[DbContext(typeof(AIDbContext))]
[Migration("20260608124957_Initial")]
partial class Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.16")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIAuditRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("ActorId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("ConversationId")
.HasColumnType("varchar(255)");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ProposalId")
.HasColumnType("varchar(255)");
b.Property<string>("Summary")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("TenantId")
.HasColumnType("varchar(255)");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("datetime(6)");
b.Property<string>("ToolInvocationId")
.HasColumnType("varchar(255)");
b.Property<string>("TraceId")
.HasColumnType("longtext");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ActorId");
b.HasIndex("ProposalId");
b.HasIndex("ToolInvocationId");
b.HasIndex("TenantId", "ConversationId");
b.HasIndex("TenantId", "Timestamp");
b.ToTable("AIAuditRecords", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIConversationRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Messages")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ProviderSessionId")
.HasColumnType("longtext");
b.Property<DateTimeOffset?>("RetentionExpiresAt")
.HasColumnType("datetime(6)");
b.Property<string>("RetentionMode")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("TenantId")
.HasColumnType("varchar(255)");
b.Property<string>("Title")
.HasColumnType("longtext");
b.Property<DateTimeOffset>("UpdatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("RetentionExpiresAt");
b.HasIndex("Status");
b.HasIndex("TenantId", "UserId");
b.ToTable("AIConversations", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIProposalRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<DateTimeOffset?>("AppliedAt")
.HasColumnType("datetime(6)");
b.Property<string>("AppliedBy")
.HasColumnType("longtext");
b.Property<string>("BaselineVersionId")
.HasColumnType("longtext");
b.Property<string>("BaselineWorkflowDefinitionId")
.HasColumnType("longtext");
b.Property<string>("ConversationId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("CreatedBy")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("GraphDiff")
.HasColumnType("longtext");
b.Property<string>("Kind")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Rationale")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTimeOffset?>("ReviewedAt")
.HasColumnType("datetime(6)");
b.Property<string>("ReviewedBy")
.HasColumnType("longtext");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("TenantId")
.HasColumnType("varchar(255)");
b.Property<string>("ValidationDiagnostics")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Warnings")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("WorkflowPayload")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("Status");
b.HasIndex("TenantId", "ConversationId");
b.ToTable("AIProposals", "Elsa");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,213 @@
using System;
using Elsa.Persistence.EFCore;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Elsa.AI.Persistence.EFCore.MySql.Migrations.AI
{
/// <inheritdoc />
public partial class Initial : Migration
{
private readonly IElsaDbContextSchema _schema;
public Initial(IElsaDbContextSchema schema)
{
_schema = schema ?? throw new ArgumentNullException(nameof(schema));
}
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.EnsureSchema(
name: _schema.Schema);
migrationBuilder.AlterDatabase()
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AIAuditRecords",
schema: _schema.Schema,
columns: table => new
{
Id = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
TenantId = table.Column<string>(type: "varchar(255)", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
ActorId = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
ConversationId = table.Column<string>(type: "varchar(255)", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
ProposalId = table.Column<string>(type: "varchar(255)", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
ToolInvocationId = table.Column<string>(type: "varchar(255)", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Type = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Timestamp = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: false),
TraceId = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Summary = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Data = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_AIAuditRecords", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AIConversations",
schema: _schema.Schema,
columns: table => new
{
Id = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
TenantId = table.Column<string>(type: "varchar(255)", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
UserId = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Title = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
Status = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedAt = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: false),
UpdatedAt = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: false),
ProviderSessionId = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
RetentionMode = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
RetentionExpiresAt = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: true),
Messages = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4")
},
constraints: table =>
{
table.PrimaryKey("PK_AIConversations", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateTable(
name: "AIProposals",
schema: _schema.Schema,
columns: table => new
{
Id = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
TenantId = table.Column<string>(type: "varchar(255)", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
ConversationId = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Kind = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Status = table.Column<string>(type: "varchar(255)", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
BaselineWorkflowDefinitionId = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
BaselineVersionId = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
WorkflowPayload = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Rationale = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Warnings = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
ValidationDiagnostics = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
GraphDiff = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedBy = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
CreatedAt = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: false),
ReviewedBy = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
ReviewedAt = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: true),
AppliedBy = table.Column<string>(type: "longtext", nullable: true)
.Annotation("MySql:CharSet", "utf8mb4"),
AppliedAt = table.Column<DateTimeOffset>(type: "datetime(6)", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AIProposals", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_ActorId",
schema: _schema.Schema,
table: "AIAuditRecords",
column: "ActorId");
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_ProposalId",
schema: _schema.Schema,
table: "AIAuditRecords",
column: "ProposalId");
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_TenantId_ConversationId",
schema: _schema.Schema,
table: "AIAuditRecords",
columns: new[] { "TenantId", "ConversationId" });
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_TenantId_Timestamp",
schema: _schema.Schema,
table: "AIAuditRecords",
columns: new[] { "TenantId", "Timestamp" });
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_ToolInvocationId",
schema: _schema.Schema,
table: "AIAuditRecords",
column: "ToolInvocationId");
migrationBuilder.CreateIndex(
name: "IX_AIConversations_RetentionExpiresAt",
schema: _schema.Schema,
table: "AIConversations",
column: "RetentionExpiresAt");
migrationBuilder.CreateIndex(
name: "IX_AIConversations_Status",
schema: _schema.Schema,
table: "AIConversations",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_AIConversations_TenantId_UserId",
schema: _schema.Schema,
table: "AIConversations",
columns: new[] { "TenantId", "UserId" });
migrationBuilder.CreateIndex(
name: "IX_AIProposals_Status",
schema: _schema.Schema,
table: "AIProposals",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_AIProposals_TenantId_ConversationId",
schema: _schema.Schema,
table: "AIProposals",
columns: new[] { "TenantId", "ConversationId" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AIAuditRecords",
schema: _schema.Schema);
migrationBuilder.DropTable(
name: "AIConversations",
schema: _schema.Schema);
migrationBuilder.DropTable(
name: "AIProposals",
schema: _schema.Schema);
}
}
}

View file

@ -0,0 +1,205 @@
// <auto-generated />
using System;
using Elsa.AI.Persistence.EFCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Elsa.AI.Persistence.EFCore.MySql.Migrations.AI
{
[DbContext(typeof(AIDbContext))]
partial class AIDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.16")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
MySqlModelBuilderExtensions.AutoIncrementColumns(modelBuilder);
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIAuditRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<string>("ActorId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("ConversationId")
.HasColumnType("varchar(255)");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ProposalId")
.HasColumnType("varchar(255)");
b.Property<string>("Summary")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("TenantId")
.HasColumnType("varchar(255)");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("datetime(6)");
b.Property<string>("ToolInvocationId")
.HasColumnType("varchar(255)");
b.Property<string>("TraceId")
.HasColumnType("longtext");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("ActorId");
b.HasIndex("ProposalId");
b.HasIndex("ToolInvocationId");
b.HasIndex("TenantId", "ConversationId");
b.HasIndex("TenantId", "Timestamp");
b.ToTable("AIAuditRecords", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIConversationRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("Messages")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("ProviderSessionId")
.HasColumnType("longtext");
b.Property<DateTimeOffset?>("RetentionExpiresAt")
.HasColumnType("datetime(6)");
b.Property<string>("RetentionMode")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("TenantId")
.HasColumnType("varchar(255)");
b.Property<string>("Title")
.HasColumnType("longtext");
b.Property<DateTimeOffset>("UpdatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("varchar(255)");
b.HasKey("Id");
b.HasIndex("RetentionExpiresAt");
b.HasIndex("Status");
b.HasIndex("TenantId", "UserId");
b.ToTable("AIConversations", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIProposalRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("varchar(255)");
b.Property<DateTimeOffset?>("AppliedAt")
.HasColumnType("datetime(6)");
b.Property<string>("AppliedBy")
.HasColumnType("longtext");
b.Property<string>("BaselineVersionId")
.HasColumnType("longtext");
b.Property<string>("BaselineWorkflowDefinitionId")
.HasColumnType("longtext");
b.Property<string>("ConversationId")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetime(6)");
b.Property<string>("CreatedBy")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("GraphDiff")
.HasColumnType("longtext");
b.Property<string>("Kind")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Rationale")
.IsRequired()
.HasColumnType("longtext");
b.Property<DateTimeOffset?>("ReviewedAt")
.HasColumnType("datetime(6)");
b.Property<string>("ReviewedBy")
.HasColumnType("longtext");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("varchar(255)");
b.Property<string>("TenantId")
.HasColumnType("varchar(255)");
b.Property<string>("ValidationDiagnostics")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("Warnings")
.IsRequired()
.HasColumnType("longtext");
b.Property<string>("WorkflowPayload")
.IsRequired()
.HasColumnType("longtext");
b.HasKey("Id");
b.HasIndex("Status");
b.HasIndex("TenantId", "ConversationId");
b.ToTable("AIProposals", "Elsa");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,34 @@
using System.Reflection;
using CShells.Features;
using Elsa.AI.Host.ShellFeatures;
using Elsa.AI.Persistence.EFCore.ShellFeatures;
using Elsa.Persistence.EFCore;
using Elsa.Persistence.EFCore.Extensions;
using Elsa.Platform.PackageManifest.Generator.Hints;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.AI.Persistence.EFCore.MySql.ShellFeatures;
[ManifestFeatureCategory("AI")]
[ManifestFeatureCategory("Persistence")]
[ShellFeature(
DisplayName = "MySQL AI Persistence",
Description = "Provides MySQL persistence for Weaver AI conversations, proposals and audit records",
DependsOn = [typeof(AIFeature)])]
[UsedImplicitly]
[ManifestInfrastructure("mysql-database", "database", Reason = "Stores Weaver AI persistence data in MySQL.", Providers = new[] { "MySQL" }, ConfigurationKeys = new[] { "ConnectionString" })]
public class MySqlAIPersistenceShellFeature : EFCoreAIPersistenceShellFeatureBase
{
protected override void ConfigureProvider(DbContextOptionsBuilder builder, Assembly migrationsAssembly, string connectionString, ElsaDbContextOptions? options)
{
builder.UseElsaMySql(migrationsAssembly, connectionString, options);
}
protected override void OnConfiguring(IServiceCollection services)
{
services.AddMySqlEntityModelCreatingHandlers();
base.OnConfiguring(services);
}
}

View file

@ -0,0 +1,19 @@
using Elsa.Persistence.EFCore;
using Elsa.Persistence.EFCore.Abstractions;
using Elsa.Persistence.EFCore.Extensions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
namespace Elsa.AI.Persistence.EFCore.Oracle;
[UsedImplicitly]
public class AIDbContextFactory : OracleAIDesignTimeDbContextFactory<AIDbContext>;
public class OracleAIDesignTimeDbContextFactory<TDbContext> : DesignTimeDbContextFactoryBase<TDbContext> where TDbContext : DbContext
{
protected override void ConfigureBuilder(DbContextOptionsBuilder<TDbContext> builder, string connectionString)
{
var options = new ElsaDbContextOptions().ConfigureOracle();
builder.UseElsaOracle(GetType().Assembly, connectionString, options);
}
}

View file

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>
Provides Oracle EF Core migrations for Elsa AI persistence.
</Description>
<PackageTags>elsa module ai persistence efcore oracle</PackageTags>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" PrivateAssets="all" />
<ProjectReference Include="..\Elsa.Persistence.EFCore.Oracle\Elsa.Persistence.EFCore.Oracle.csproj" />
<ProjectReference Include="..\Elsa.AI.Host\Elsa.AI.Host.csproj" />
<ProjectReference Include="..\Elsa.AI.Persistence.EFCore\Elsa.AI.Persistence.EFCore.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,30 @@
using System.Reflection;
using Elsa.AI.Persistence.EFCore.Features;
using Elsa.Persistence.EFCore;
using Elsa.Persistence.EFCore.Extensions;
using Oracle.EntityFrameworkCore.Infrastructure;
namespace Elsa.AI.Persistence.EFCore.Oracle.Extensions;
public static class OracleAIPersistenceFeatureExtensions
{
private static Assembly Assembly => typeof(OracleAIPersistenceFeatureExtensions).Assembly;
public static AIPersistenceFeature UseOracle(
this AIPersistenceFeature feature,
string connectionString,
ElsaDbContextOptions? options = null,
Action<OracleDbContextOptionsBuilder>? configure = null)
{
return feature.UseOracle(_ => connectionString, options, configure);
}
public static AIPersistenceFeature UseOracle(
this AIPersistenceFeature feature,
Func<IServiceProvider, string> connectionStringFunc,
ElsaDbContextOptions? options = null,
Action<OracleDbContextOptionsBuilder>? configure = null)
{
return feature.UseOracle(Assembly, connectionStringFunc, options, configure);
}
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

View file

@ -0,0 +1,208 @@
// <auto-generated />
using System;
using Elsa.AI.Persistence.EFCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Oracle.EntityFrameworkCore.Metadata;
#nullable disable
namespace Elsa.AI.Persistence.EFCore.Oracle.Migrations.AI
{
[DbContext(typeof(AIDbContext))]
[Migration("20260608125003_Initial")]
partial class Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.16")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIAuditRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ActorId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ConversationId")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("ProposalId")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("Summary")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("TenantId")
.HasColumnType("NVARCHAR2(450)");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("ToolInvocationId")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("TraceId")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.HasKey("Id");
b.HasIndex("ActorId");
b.HasIndex("ProposalId");
b.HasIndex("ToolInvocationId");
b.HasIndex("TenantId", "ConversationId");
b.HasIndex("TenantId", "Timestamp");
b.ToTable("AIAuditRecords", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIConversationRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("NVARCHAR2(450)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("Messages")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("ProviderSessionId")
.HasColumnType("NVARCHAR2(2000)");
b.Property<DateTimeOffset?>("RetentionExpiresAt")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("RetentionMode")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("TenantId")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("Title")
.HasColumnType("NVARCHAR2(2000)");
b.Property<DateTimeOffset>("UpdatedAt")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.HasKey("Id");
b.HasIndex("RetentionExpiresAt");
b.HasIndex("Status");
b.HasIndex("TenantId", "UserId");
b.ToTable("AIConversations", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIProposalRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("NVARCHAR2(450)");
b.Property<DateTimeOffset?>("AppliedAt")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("AppliedBy")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("BaselineVersionId")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("BaselineWorkflowDefinitionId")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("ConversationId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("CreatedBy")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("GraphDiff")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("Kind")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("Rationale")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<DateTimeOffset?>("ReviewedAt")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("ReviewedBy")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("TenantId")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ValidationDiagnostics")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("Warnings")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("WorkflowPayload")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.HasKey("Id");
b.HasIndex("Status");
b.HasIndex("TenantId", "ConversationId");
b.ToTable("AIProposals", "Elsa");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,174 @@
using System;
using Elsa.Persistence.EFCore;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Elsa.AI.Persistence.EFCore.Oracle.Migrations.AI
{
/// <inheritdoc />
public partial class Initial : Migration
{
private readonly IElsaDbContextSchema _schema;
public Initial(IElsaDbContextSchema schema)
{
_schema = schema ?? throw new ArgumentNullException(nameof(schema));
}
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.EnsureSchema(
name: _schema.Schema);
migrationBuilder.CreateTable(
name: "AIAuditRecords",
schema: _schema.Schema,
columns: table => new
{
Id = table.Column<string>(type: "NVARCHAR2(450)", nullable: false),
TenantId = table.Column<string>(type: "NVARCHAR2(450)", nullable: true),
ActorId = table.Column<string>(type: "NVARCHAR2(450)", nullable: false),
ConversationId = table.Column<string>(type: "NVARCHAR2(450)", nullable: true),
ProposalId = table.Column<string>(type: "NVARCHAR2(450)", nullable: true),
ToolInvocationId = table.Column<string>(type: "NVARCHAR2(450)", nullable: true),
Type = table.Column<string>(type: "NVARCHAR2(2000)", nullable: false),
Timestamp = table.Column<DateTimeOffset>(type: "TIMESTAMP(7) WITH TIME ZONE", nullable: false),
TraceId = table.Column<string>(type: "NVARCHAR2(2000)", nullable: true),
Summary = table.Column<string>(type: "NVARCHAR2(2000)", nullable: false),
Data = table.Column<string>(type: "NVARCHAR2(2000)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AIAuditRecords", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AIConversations",
schema: _schema.Schema,
columns: table => new
{
Id = table.Column<string>(type: "NVARCHAR2(450)", nullable: false),
TenantId = table.Column<string>(type: "NVARCHAR2(450)", nullable: true),
UserId = table.Column<string>(type: "NVARCHAR2(450)", nullable: false),
Title = table.Column<string>(type: "NVARCHAR2(2000)", nullable: true),
Status = table.Column<string>(type: "NVARCHAR2(450)", nullable: false),
CreatedAt = table.Column<DateTimeOffset>(type: "TIMESTAMP(7) WITH TIME ZONE", nullable: false),
UpdatedAt = table.Column<DateTimeOffset>(type: "TIMESTAMP(7) WITH TIME ZONE", nullable: false),
ProviderSessionId = table.Column<string>(type: "NVARCHAR2(2000)", nullable: true),
RetentionMode = table.Column<string>(type: "NVARCHAR2(2000)", nullable: false),
RetentionExpiresAt = table.Column<DateTimeOffset>(type: "TIMESTAMP(7) WITH TIME ZONE", nullable: true),
Messages = table.Column<string>(type: "NVARCHAR2(2000)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AIConversations", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AIProposals",
schema: _schema.Schema,
columns: table => new
{
Id = table.Column<string>(type: "NVARCHAR2(450)", nullable: false),
TenantId = table.Column<string>(type: "NVARCHAR2(450)", nullable: true),
ConversationId = table.Column<string>(type: "NVARCHAR2(450)", nullable: false),
Kind = table.Column<string>(type: "NVARCHAR2(2000)", nullable: false),
Status = table.Column<string>(type: "NVARCHAR2(450)", nullable: false),
BaselineWorkflowDefinitionId = table.Column<string>(type: "NVARCHAR2(2000)", nullable: true),
BaselineVersionId = table.Column<string>(type: "NVARCHAR2(2000)", nullable: true),
WorkflowPayload = table.Column<string>(type: "NVARCHAR2(2000)", nullable: false),
Rationale = table.Column<string>(type: "NVARCHAR2(2000)", nullable: false),
Warnings = table.Column<string>(type: "NVARCHAR2(2000)", nullable: false),
ValidationDiagnostics = table.Column<string>(type: "NVARCHAR2(2000)", nullable: false),
GraphDiff = table.Column<string>(type: "NVARCHAR2(2000)", nullable: true),
CreatedBy = table.Column<string>(type: "NVARCHAR2(2000)", nullable: false),
CreatedAt = table.Column<DateTimeOffset>(type: "TIMESTAMP(7) WITH TIME ZONE", nullable: false),
ReviewedBy = table.Column<string>(type: "NVARCHAR2(2000)", nullable: true),
ReviewedAt = table.Column<DateTimeOffset>(type: "TIMESTAMP(7) WITH TIME ZONE", nullable: true),
AppliedBy = table.Column<string>(type: "NVARCHAR2(2000)", nullable: true),
AppliedAt = table.Column<DateTimeOffset>(type: "TIMESTAMP(7) WITH TIME ZONE", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AIProposals", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_ActorId",
schema: _schema.Schema,
table: "AIAuditRecords",
column: "ActorId");
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_ProposalId",
schema: _schema.Schema,
table: "AIAuditRecords",
column: "ProposalId");
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_TenantId_ConversationId",
schema: _schema.Schema,
table: "AIAuditRecords",
columns: new[] { "TenantId", "ConversationId" });
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_TenantId_Timestamp",
schema: _schema.Schema,
table: "AIAuditRecords",
columns: new[] { "TenantId", "Timestamp" });
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_ToolInvocationId",
schema: _schema.Schema,
table: "AIAuditRecords",
column: "ToolInvocationId");
migrationBuilder.CreateIndex(
name: "IX_AIConversations_RetentionExpiresAt",
schema: _schema.Schema,
table: "AIConversations",
column: "RetentionExpiresAt");
migrationBuilder.CreateIndex(
name: "IX_AIConversations_Status",
schema: _schema.Schema,
table: "AIConversations",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_AIConversations_TenantId_UserId",
schema: _schema.Schema,
table: "AIConversations",
columns: new[] { "TenantId", "UserId" });
migrationBuilder.CreateIndex(
name: "IX_AIProposals_Status",
schema: _schema.Schema,
table: "AIProposals",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_AIProposals_TenantId_ConversationId",
schema: _schema.Schema,
table: "AIProposals",
columns: new[] { "TenantId", "ConversationId" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AIAuditRecords",
schema: _schema.Schema);
migrationBuilder.DropTable(
name: "AIConversations",
schema: _schema.Schema);
migrationBuilder.DropTable(
name: "AIProposals",
schema: _schema.Schema);
}
}
}

View file

@ -0,0 +1,205 @@
// <auto-generated />
using System;
using Elsa.AI.Persistence.EFCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Oracle.EntityFrameworkCore.Metadata;
#nullable disable
namespace Elsa.AI.Persistence.EFCore.Oracle.Migrations.AI
{
[DbContext(typeof(AIDbContext))]
partial class AIDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.16")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
OracleModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIAuditRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ActorId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ConversationId")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("ProposalId")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("Summary")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("TenantId")
.HasColumnType("NVARCHAR2(450)");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("ToolInvocationId")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("TraceId")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.HasKey("Id");
b.HasIndex("ActorId");
b.HasIndex("ProposalId");
b.HasIndex("ToolInvocationId");
b.HasIndex("TenantId", "ConversationId");
b.HasIndex("TenantId", "Timestamp");
b.ToTable("AIAuditRecords", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIConversationRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("NVARCHAR2(450)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("Messages")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("ProviderSessionId")
.HasColumnType("NVARCHAR2(2000)");
b.Property<DateTimeOffset?>("RetentionExpiresAt")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("RetentionMode")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("TenantId")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("Title")
.HasColumnType("NVARCHAR2(2000)");
b.Property<DateTimeOffset>("UpdatedAt")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.HasKey("Id");
b.HasIndex("RetentionExpiresAt");
b.HasIndex("Status");
b.HasIndex("TenantId", "UserId");
b.ToTable("AIConversations", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIProposalRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("NVARCHAR2(450)");
b.Property<DateTimeOffset?>("AppliedAt")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("AppliedBy")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("BaselineVersionId")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("BaselineWorkflowDefinitionId")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("ConversationId")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("CreatedBy")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("GraphDiff")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("Kind")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("Rationale")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<DateTimeOffset?>("ReviewedAt")
.HasColumnType("TIMESTAMP(7) WITH TIME ZONE");
b.Property<string>("ReviewedBy")
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("TenantId")
.HasColumnType("NVARCHAR2(450)");
b.Property<string>("ValidationDiagnostics")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("Warnings")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.Property<string>("WorkflowPayload")
.IsRequired()
.HasColumnType("NVARCHAR2(2000)");
b.HasKey("Id");
b.HasIndex("Status");
b.HasIndex("TenantId", "ConversationId");
b.ToTable("AIProposals", "Elsa");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,28 @@
using System.Reflection;
using CShells.Features;
using Elsa.AI.Host.ShellFeatures;
using Elsa.AI.Persistence.EFCore.ShellFeatures;
using Elsa.Persistence.EFCore;
using Elsa.Persistence.EFCore.Extensions;
using Elsa.Platform.PackageManifest.Generator.Hints;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
namespace Elsa.AI.Persistence.EFCore.Oracle.ShellFeatures;
[ManifestFeatureCategory("AI")]
[ManifestFeatureCategory("Persistence")]
[ShellFeature(
DisplayName = "Oracle AI Persistence",
Description = "Provides Oracle persistence for Weaver AI conversations, proposals and audit records",
DependsOn = [typeof(AIFeature)])]
[UsedImplicitly]
[ManifestInfrastructure("oracle-database", "database", Reason = "Stores Weaver AI persistence data in Oracle Database.", Providers = new[] { "Oracle" }, ConfigurationKeys = new[] { "ConnectionString" })]
public class OracleAIPersistenceShellFeature : EFCoreAIPersistenceShellFeatureBase
{
protected override void ConfigureProvider(DbContextOptionsBuilder builder, Assembly migrationsAssembly, string connectionString, ElsaDbContextOptions? options)
{
options = options.ConfigureOracle();
builder.UseElsaOracle(migrationsAssembly, connectionString, options);
}
}

View file

@ -0,0 +1,23 @@
using Elsa.Persistence.EFCore.Abstractions;
using Elsa.Persistence.EFCore.Extensions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.AI.Persistence.EFCore.PostgreSql;
[UsedImplicitly]
public class AIDbContextFactory : PostgreSqlAIDesignTimeDbContextFactory<AIDbContext>;
public class PostgreSqlAIDesignTimeDbContextFactory<TDbContext> : DesignTimeDbContextFactoryBase<TDbContext> where TDbContext : DbContext
{
protected override void ConfigureServices(IServiceCollection services)
{
services.AddPostgreSqlEntityModelCreatingHandlers();
}
protected override void ConfigureBuilder(DbContextOptionsBuilder<TDbContext> builder, string connectionString)
{
builder.UseElsaPostgreSql(GetType().Assembly, connectionString);
}
}

View file

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>
Provides PostgreSQL EF Core migrations for Elsa AI persistence.
</Description>
<PackageTags>elsa module ai persistence efcore postgresql</PackageTags>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" PrivateAssets="all" />
<ProjectReference Include="..\Elsa.Persistence.EFCore.PostgreSql\Elsa.Persistence.EFCore.PostgreSql.csproj" />
<ProjectReference Include="..\Elsa.AI.Host\Elsa.AI.Host.csproj" />
<ProjectReference Include="..\Elsa.AI.Persistence.EFCore\Elsa.AI.Persistence.EFCore.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,30 @@
using System.Reflection;
using Elsa.AI.Persistence.EFCore.Features;
using Elsa.Persistence.EFCore;
using Elsa.Persistence.EFCore.Extensions;
using Npgsql.EntityFrameworkCore.PostgreSQL.Infrastructure;
namespace Elsa.AI.Persistence.EFCore.PostgreSql.Extensions;
public static class PostgreSqlAIPersistenceFeatureExtensions
{
private static Assembly Assembly => typeof(PostgreSqlAIPersistenceFeatureExtensions).Assembly;
public static AIPersistenceFeature UsePostgreSql(
this AIPersistenceFeature feature,
string connectionString,
ElsaDbContextOptions? options = null,
Action<NpgsqlDbContextOptionsBuilder>? configure = null)
{
return feature.UsePostgreSql(_ => connectionString, options, configure);
}
public static AIPersistenceFeature UsePostgreSql(
this AIPersistenceFeature feature,
Func<IServiceProvider, string> connectionStringFunc,
ElsaDbContextOptions? options = null,
Action<NpgsqlDbContextOptionsBuilder>? configure = null)
{
return feature.UsePostgreSql(Assembly, connectionStringFunc, options, configure);
}
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

View file

@ -0,0 +1,208 @@
// <auto-generated />
using System;
using Elsa.AI.Persistence.EFCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Elsa.AI.Persistence.EFCore.PostgreSql.Migrations.AI
{
[DbContext(typeof(AIDbContext))]
[Migration("20260608124950_Initial")]
partial class Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.16")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIAuditRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("ActorId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ConversationId")
.HasColumnType("text");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ProposalId")
.HasColumnType("text");
b.Property<string>("Summary")
.IsRequired()
.HasColumnType("text");
b.Property<string>("TenantId")
.HasColumnType("text");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("timestamp with time zone");
b.Property<string>("ToolInvocationId")
.HasColumnType("text");
b.Property<string>("TraceId")
.HasColumnType("text");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("ActorId");
b.HasIndex("ProposalId");
b.HasIndex("ToolInvocationId");
b.HasIndex("TenantId", "ConversationId");
b.HasIndex("TenantId", "Timestamp");
b.ToTable("AIAuditRecords", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIConversationRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Messages")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ProviderSessionId")
.HasColumnType("text");
b.Property<DateTimeOffset?>("RetentionExpiresAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("RetentionMode")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("text");
b.Property<string>("TenantId")
.HasColumnType("text");
b.Property<string>("Title")
.HasColumnType("text");
b.Property<DateTimeOffset>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("RetentionExpiresAt");
b.HasIndex("Status");
b.HasIndex("TenantId", "UserId");
b.ToTable("AIConversations", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIProposalRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<DateTimeOffset?>("AppliedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("AppliedBy")
.HasColumnType("text");
b.Property<string>("BaselineVersionId")
.HasColumnType("text");
b.Property<string>("BaselineWorkflowDefinitionId")
.HasColumnType("text");
b.Property<string>("ConversationId")
.IsRequired()
.HasColumnType("text");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("CreatedBy")
.IsRequired()
.HasColumnType("text");
b.Property<string>("GraphDiff")
.HasColumnType("text");
b.Property<string>("Kind")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Rationale")
.IsRequired()
.HasColumnType("text");
b.Property<DateTimeOffset?>("ReviewedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("ReviewedBy")
.HasColumnType("text");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("text");
b.Property<string>("TenantId")
.HasColumnType("text");
b.Property<string>("ValidationDiagnostics")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Warnings")
.IsRequired()
.HasColumnType("text");
b.Property<string>("WorkflowPayload")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("Status");
b.HasIndex("TenantId", "ConversationId");
b.ToTable("AIProposals", "Elsa");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,174 @@
using System;
using Elsa.Persistence.EFCore;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Elsa.AI.Persistence.EFCore.PostgreSql.Migrations.AI
{
/// <inheritdoc />
public partial class Initial : Migration
{
private readonly IElsaDbContextSchema _schema;
public Initial(IElsaDbContextSchema schema)
{
_schema = schema ?? throw new ArgumentNullException(nameof(schema));
}
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.EnsureSchema(
name: _schema.Schema);
migrationBuilder.CreateTable(
name: "AIAuditRecords",
schema: _schema.Schema,
columns: table => new
{
Id = table.Column<string>(type: "text", nullable: false),
TenantId = table.Column<string>(type: "text", nullable: true),
ActorId = table.Column<string>(type: "text", nullable: false),
ConversationId = table.Column<string>(type: "text", nullable: true),
ProposalId = table.Column<string>(type: "text", nullable: true),
ToolInvocationId = table.Column<string>(type: "text", nullable: true),
Type = table.Column<string>(type: "text", nullable: false),
Timestamp = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
TraceId = table.Column<string>(type: "text", nullable: true),
Summary = table.Column<string>(type: "text", nullable: false),
Data = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AIAuditRecords", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AIConversations",
schema: _schema.Schema,
columns: table => new
{
Id = table.Column<string>(type: "text", nullable: false),
TenantId = table.Column<string>(type: "text", nullable: true),
UserId = table.Column<string>(type: "text", nullable: false),
Title = table.Column<string>(type: "text", nullable: true),
Status = table.Column<string>(type: "text", nullable: false),
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
UpdatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
ProviderSessionId = table.Column<string>(type: "text", nullable: true),
RetentionMode = table.Column<string>(type: "text", nullable: false),
RetentionExpiresAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
Messages = table.Column<string>(type: "text", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AIConversations", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AIProposals",
schema: _schema.Schema,
columns: table => new
{
Id = table.Column<string>(type: "text", nullable: false),
TenantId = table.Column<string>(type: "text", nullable: true),
ConversationId = table.Column<string>(type: "text", nullable: false),
Kind = table.Column<string>(type: "text", nullable: false),
Status = table.Column<string>(type: "text", nullable: false),
BaselineWorkflowDefinitionId = table.Column<string>(type: "text", nullable: true),
BaselineVersionId = table.Column<string>(type: "text", nullable: true),
WorkflowPayload = table.Column<string>(type: "text", nullable: false),
Rationale = table.Column<string>(type: "text", nullable: false),
Warnings = table.Column<string>(type: "text", nullable: false),
ValidationDiagnostics = table.Column<string>(type: "text", nullable: false),
GraphDiff = table.Column<string>(type: "text", nullable: true),
CreatedBy = table.Column<string>(type: "text", nullable: false),
CreatedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: false),
ReviewedBy = table.Column<string>(type: "text", nullable: true),
ReviewedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true),
AppliedBy = table.Column<string>(type: "text", nullable: true),
AppliedAt = table.Column<DateTimeOffset>(type: "timestamp with time zone", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AIProposals", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_ActorId",
schema: _schema.Schema,
table: "AIAuditRecords",
column: "ActorId");
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_ProposalId",
schema: _schema.Schema,
table: "AIAuditRecords",
column: "ProposalId");
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_TenantId_ConversationId",
schema: _schema.Schema,
table: "AIAuditRecords",
columns: new[] { "TenantId", "ConversationId" });
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_TenantId_Timestamp",
schema: _schema.Schema,
table: "AIAuditRecords",
columns: new[] { "TenantId", "Timestamp" });
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_ToolInvocationId",
schema: _schema.Schema,
table: "AIAuditRecords",
column: "ToolInvocationId");
migrationBuilder.CreateIndex(
name: "IX_AIConversations_RetentionExpiresAt",
schema: _schema.Schema,
table: "AIConversations",
column: "RetentionExpiresAt");
migrationBuilder.CreateIndex(
name: "IX_AIConversations_Status",
schema: _schema.Schema,
table: "AIConversations",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_AIConversations_TenantId_UserId",
schema: _schema.Schema,
table: "AIConversations",
columns: new[] { "TenantId", "UserId" });
migrationBuilder.CreateIndex(
name: "IX_AIProposals_Status",
schema: _schema.Schema,
table: "AIProposals",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_AIProposals_TenantId_ConversationId",
schema: _schema.Schema,
table: "AIProposals",
columns: new[] { "TenantId", "ConversationId" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AIAuditRecords",
schema: _schema.Schema);
migrationBuilder.DropTable(
name: "AIConversations",
schema: _schema.Schema);
migrationBuilder.DropTable(
name: "AIProposals",
schema: _schema.Schema);
}
}
}

View file

@ -0,0 +1,205 @@
// <auto-generated />
using System;
using Elsa.AI.Persistence.EFCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
#nullable disable
namespace Elsa.AI.Persistence.EFCore.PostgreSql.Migrations.AI
{
[DbContext(typeof(AIDbContext))]
partial class AIDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.16")
.HasAnnotation("Relational:MaxIdentifierLength", 63);
NpgsqlModelBuilderExtensions.UseIdentityByDefaultColumns(modelBuilder);
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIAuditRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<string>("ActorId")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ConversationId")
.HasColumnType("text");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ProposalId")
.HasColumnType("text");
b.Property<string>("Summary")
.IsRequired()
.HasColumnType("text");
b.Property<string>("TenantId")
.HasColumnType("text");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("timestamp with time zone");
b.Property<string>("ToolInvocationId")
.HasColumnType("text");
b.Property<string>("TraceId")
.HasColumnType("text");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("ActorId");
b.HasIndex("ProposalId");
b.HasIndex("ToolInvocationId");
b.HasIndex("TenantId", "ConversationId");
b.HasIndex("TenantId", "Timestamp");
b.ToTable("AIAuditRecords", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIConversationRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("Messages")
.IsRequired()
.HasColumnType("text");
b.Property<string>("ProviderSessionId")
.HasColumnType("text");
b.Property<DateTimeOffset?>("RetentionExpiresAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("RetentionMode")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("text");
b.Property<string>("TenantId")
.HasColumnType("text");
b.Property<string>("Title")
.HasColumnType("text");
b.Property<DateTimeOffset>("UpdatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("RetentionExpiresAt");
b.HasIndex("Status");
b.HasIndex("TenantId", "UserId");
b.ToTable("AIConversations", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIProposalRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("text");
b.Property<DateTimeOffset?>("AppliedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("AppliedBy")
.HasColumnType("text");
b.Property<string>("BaselineVersionId")
.HasColumnType("text");
b.Property<string>("BaselineWorkflowDefinitionId")
.HasColumnType("text");
b.Property<string>("ConversationId")
.IsRequired()
.HasColumnType("text");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("CreatedBy")
.IsRequired()
.HasColumnType("text");
b.Property<string>("GraphDiff")
.HasColumnType("text");
b.Property<string>("Kind")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Rationale")
.IsRequired()
.HasColumnType("text");
b.Property<DateTimeOffset?>("ReviewedAt")
.HasColumnType("timestamp with time zone");
b.Property<string>("ReviewedBy")
.HasColumnType("text");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("text");
b.Property<string>("TenantId")
.HasColumnType("text");
b.Property<string>("ValidationDiagnostics")
.IsRequired()
.HasColumnType("text");
b.Property<string>("Warnings")
.IsRequired()
.HasColumnType("text");
b.Property<string>("WorkflowPayload")
.IsRequired()
.HasColumnType("text");
b.HasKey("Id");
b.HasIndex("Status");
b.HasIndex("TenantId", "ConversationId");
b.ToTable("AIProposals", "Elsa");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,34 @@
using System.Reflection;
using CShells.Features;
using Elsa.AI.Host.ShellFeatures;
using Elsa.AI.Persistence.EFCore.ShellFeatures;
using Elsa.Persistence.EFCore;
using Elsa.Persistence.EFCore.Extensions;
using Elsa.Platform.PackageManifest.Generator.Hints;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.AI.Persistence.EFCore.PostgreSql.ShellFeatures;
[ManifestFeatureCategory("AI")]
[ManifestFeatureCategory("Persistence")]
[ShellFeature(
DisplayName = "PostgreSQL AI Persistence",
Description = "Provides PostgreSQL persistence for Weaver AI conversations, proposals and audit records",
DependsOn = [typeof(AIFeature)])]
[UsedImplicitly]
[ManifestInfrastructure("postgresql-database", "database", Reason = "Stores Weaver AI persistence data in PostgreSQL.", Providers = new[] { "PostgreSQL" }, ConfigurationKeys = new[] { "ConnectionString" })]
public class PostgreSqlAIPersistenceShellFeature : EFCoreAIPersistenceShellFeatureBase
{
protected override void ConfigureProvider(DbContextOptionsBuilder builder, Assembly migrationsAssembly, string connectionString, ElsaDbContextOptions? options)
{
builder.UseElsaPostgreSql(migrationsAssembly, connectionString, options);
}
protected override void OnConfiguring(IServiceCollection services)
{
services.AddPostgreSqlEntityModelCreatingHandlers();
base.OnConfiguring(services);
}
}

View file

@ -0,0 +1,17 @@
using Elsa.Persistence.EFCore.Abstractions;
using Elsa.Persistence.EFCore.Extensions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
namespace Elsa.AI.Persistence.EFCore.SqlServer;
[UsedImplicitly]
public class AIDbContextFactory : SqlServerAIDesignTimeDbContextFactory<AIDbContext>;
public class SqlServerAIDesignTimeDbContextFactory<TDbContext> : DesignTimeDbContextFactoryBase<TDbContext> where TDbContext : DbContext
{
protected override void ConfigureBuilder(DbContextOptionsBuilder<TDbContext> builder, string connectionString)
{
builder.UseElsaSqlServer(GetType().Assembly, connectionString);
}
}

View file

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>
Provides SQL Server EF Core migrations for Elsa AI persistence.
</Description>
<PackageTags>elsa module ai persistence efcore sqlserver</PackageTags>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" PrivateAssets="all" />
<ProjectReference Include="..\Elsa.Persistence.EFCore.SqlServer\Elsa.Persistence.EFCore.SqlServer.csproj" />
<ProjectReference Include="..\Elsa.AI.Host\Elsa.AI.Host.csproj" />
<ProjectReference Include="..\Elsa.AI.Persistence.EFCore\Elsa.AI.Persistence.EFCore.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,30 @@
using System.Reflection;
using Elsa.AI.Persistence.EFCore.Features;
using Elsa.Persistence.EFCore;
using Elsa.Persistence.EFCore.Extensions;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace Elsa.AI.Persistence.EFCore.SqlServer.Extensions;
public static class SqlServerAIPersistenceFeatureExtensions
{
private static Assembly Assembly => typeof(SqlServerAIPersistenceFeatureExtensions).Assembly;
public static AIPersistenceFeature UseSqlServer(
this AIPersistenceFeature feature,
string connectionString,
ElsaDbContextOptions? options = null,
Action<SqlServerDbContextOptionsBuilder>? configure = null)
{
return feature.UseSqlServer(_ => connectionString, options, configure);
}
public static AIPersistenceFeature UseSqlServer(
this AIPersistenceFeature feature,
Func<IServiceProvider, string> connectionStringFunc,
ElsaDbContextOptions? options = null,
Action<SqlServerDbContextOptionsBuilder>? configure = null)
{
return feature.UseSqlServer(Assembly, connectionStringFunc, options, configure);
}
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

View file

@ -0,0 +1,208 @@
// <auto-generated />
using System;
using Elsa.AI.Persistence.EFCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Elsa.AI.Persistence.EFCore.SqlServer.Migrations.AI
{
[DbContext(typeof(AIDbContext))]
[Migration("20260608124944_Initial")]
partial class Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.16")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIAuditRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ActorId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("ConversationId")
.HasColumnType("nvarchar(450)");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("ProposalId")
.HasColumnType("nvarchar(450)");
b.Property<string>("Summary")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("TenantId")
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("datetimeoffset");
b.Property<string>("ToolInvocationId")
.HasColumnType("nvarchar(450)");
b.Property<string>("TraceId")
.HasColumnType("nvarchar(max)");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("ActorId");
b.HasIndex("ProposalId");
b.HasIndex("ToolInvocationId");
b.HasIndex("TenantId", "ConversationId");
b.HasIndex("TenantId", "Timestamp");
b.ToTable("AIAuditRecords", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIConversationRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("Messages")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("ProviderSessionId")
.HasColumnType("nvarchar(max)");
b.Property<DateTimeOffset?>("RetentionExpiresAt")
.HasColumnType("datetimeoffset");
b.Property<string>("RetentionMode")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("TenantId")
.HasColumnType("nvarchar(450)");
b.Property<string>("Title")
.HasColumnType("nvarchar(max)");
b.Property<DateTimeOffset>("UpdatedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RetentionExpiresAt");
b.HasIndex("Status");
b.HasIndex("TenantId", "UserId");
b.ToTable("AIConversations", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIProposalRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset?>("AppliedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("AppliedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("BaselineVersionId")
.HasColumnType("nvarchar(max)");
b.Property<string>("BaselineWorkflowDefinitionId")
.HasColumnType("nvarchar(max)");
b.Property<string>("ConversationId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("CreatedBy")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("GraphDiff")
.HasColumnType("nvarchar(max)");
b.Property<string>("Kind")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Rationale")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<DateTimeOffset?>("ReviewedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("ReviewedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("TenantId")
.HasColumnType("nvarchar(450)");
b.Property<string>("ValidationDiagnostics")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Warnings")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("WorkflowPayload")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("Status");
b.HasIndex("TenantId", "ConversationId");
b.ToTable("AIProposals", "Elsa");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,174 @@
using System;
using Elsa.Persistence.EFCore;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Elsa.AI.Persistence.EFCore.SqlServer.Migrations.AI
{
/// <inheritdoc />
public partial class Initial : Migration
{
private readonly IElsaDbContextSchema _schema;
public Initial(IElsaDbContextSchema schema)
{
_schema = schema ?? throw new ArgumentNullException(nameof(schema));
}
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.EnsureSchema(
name: _schema.Schema);
migrationBuilder.CreateTable(
name: "AIAuditRecords",
schema: _schema.Schema,
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(450)", nullable: false),
TenantId = table.Column<string>(type: "nvarchar(450)", nullable: true),
ActorId = table.Column<string>(type: "nvarchar(450)", nullable: false),
ConversationId = table.Column<string>(type: "nvarchar(450)", nullable: true),
ProposalId = table.Column<string>(type: "nvarchar(450)", nullable: true),
ToolInvocationId = table.Column<string>(type: "nvarchar(450)", nullable: true),
Type = table.Column<string>(type: "nvarchar(max)", nullable: false),
Timestamp = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
TraceId = table.Column<string>(type: "nvarchar(max)", nullable: true),
Summary = table.Column<string>(type: "nvarchar(max)", nullable: false),
Data = table.Column<string>(type: "nvarchar(max)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AIAuditRecords", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AIConversations",
schema: _schema.Schema,
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(450)", nullable: false),
TenantId = table.Column<string>(type: "nvarchar(450)", nullable: true),
UserId = table.Column<string>(type: "nvarchar(450)", nullable: false),
Title = table.Column<string>(type: "nvarchar(max)", nullable: true),
Status = table.Column<string>(type: "nvarchar(450)", nullable: false),
CreatedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
UpdatedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
ProviderSessionId = table.Column<string>(type: "nvarchar(max)", nullable: true),
RetentionMode = table.Column<string>(type: "nvarchar(max)", nullable: false),
RetentionExpiresAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true),
Messages = table.Column<string>(type: "nvarchar(max)", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AIConversations", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AIProposals",
schema: _schema.Schema,
columns: table => new
{
Id = table.Column<string>(type: "nvarchar(450)", nullable: false),
TenantId = table.Column<string>(type: "nvarchar(450)", nullable: true),
ConversationId = table.Column<string>(type: "nvarchar(450)", nullable: false),
Kind = table.Column<string>(type: "nvarchar(max)", nullable: false),
Status = table.Column<string>(type: "nvarchar(450)", nullable: false),
BaselineWorkflowDefinitionId = table.Column<string>(type: "nvarchar(max)", nullable: true),
BaselineVersionId = table.Column<string>(type: "nvarchar(max)", nullable: true),
WorkflowPayload = table.Column<string>(type: "nvarchar(max)", nullable: false),
Rationale = table.Column<string>(type: "nvarchar(max)", nullable: false),
Warnings = table.Column<string>(type: "nvarchar(max)", nullable: false),
ValidationDiagnostics = table.Column<string>(type: "nvarchar(max)", nullable: false),
GraphDiff = table.Column<string>(type: "nvarchar(max)", nullable: true),
CreatedBy = table.Column<string>(type: "nvarchar(max)", nullable: false),
CreatedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: false),
ReviewedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
ReviewedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true),
AppliedBy = table.Column<string>(type: "nvarchar(max)", nullable: true),
AppliedAt = table.Column<DateTimeOffset>(type: "datetimeoffset", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AIProposals", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_ActorId",
schema: _schema.Schema,
table: "AIAuditRecords",
column: "ActorId");
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_ProposalId",
schema: _schema.Schema,
table: "AIAuditRecords",
column: "ProposalId");
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_TenantId_ConversationId",
schema: _schema.Schema,
table: "AIAuditRecords",
columns: new[] { "TenantId", "ConversationId" });
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_TenantId_Timestamp",
schema: _schema.Schema,
table: "AIAuditRecords",
columns: new[] { "TenantId", "Timestamp" });
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_ToolInvocationId",
schema: _schema.Schema,
table: "AIAuditRecords",
column: "ToolInvocationId");
migrationBuilder.CreateIndex(
name: "IX_AIConversations_RetentionExpiresAt",
schema: _schema.Schema,
table: "AIConversations",
column: "RetentionExpiresAt");
migrationBuilder.CreateIndex(
name: "IX_AIConversations_Status",
schema: _schema.Schema,
table: "AIConversations",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_AIConversations_TenantId_UserId",
schema: _schema.Schema,
table: "AIConversations",
columns: new[] { "TenantId", "UserId" });
migrationBuilder.CreateIndex(
name: "IX_AIProposals_Status",
schema: _schema.Schema,
table: "AIProposals",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_AIProposals_TenantId_ConversationId",
schema: _schema.Schema,
table: "AIProposals",
columns: new[] { "TenantId", "ConversationId" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AIAuditRecords",
schema: _schema.Schema);
migrationBuilder.DropTable(
name: "AIConversations",
schema: _schema.Schema);
migrationBuilder.DropTable(
name: "AIProposals",
schema: _schema.Schema);
}
}
}

View file

@ -0,0 +1,205 @@
// <auto-generated />
using System;
using Elsa.AI.Persistence.EFCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Elsa.AI.Persistence.EFCore.SqlServer.Migrations.AI
{
[DbContext(typeof(AIDbContext))]
partial class AIDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.16")
.HasAnnotation("Relational:MaxIdentifierLength", 128);
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIAuditRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<string>("ActorId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("ConversationId")
.HasColumnType("nvarchar(450)");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("ProposalId")
.HasColumnType("nvarchar(450)");
b.Property<string>("Summary")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("TenantId")
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset>("Timestamp")
.HasColumnType("datetimeoffset");
b.Property<string>("ToolInvocationId")
.HasColumnType("nvarchar(450)");
b.Property<string>("TraceId")
.HasColumnType("nvarchar(max)");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("ActorId");
b.HasIndex("ProposalId");
b.HasIndex("ToolInvocationId");
b.HasIndex("TenantId", "ConversationId");
b.HasIndex("TenantId", "Timestamp");
b.ToTable("AIAuditRecords", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIConversationRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("Messages")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("ProviderSessionId")
.HasColumnType("nvarchar(max)");
b.Property<DateTimeOffset?>("RetentionExpiresAt")
.HasColumnType("datetimeoffset");
b.Property<string>("RetentionMode")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("TenantId")
.HasColumnType("nvarchar(450)");
b.Property<string>("Title")
.HasColumnType("nvarchar(max)");
b.Property<DateTimeOffset>("UpdatedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.HasKey("Id");
b.HasIndex("RetentionExpiresAt");
b.HasIndex("Status");
b.HasIndex("TenantId", "UserId");
b.ToTable("AIConversations", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIProposalRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset?>("AppliedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("AppliedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("BaselineVersionId")
.HasColumnType("nvarchar(max)");
b.Property<string>("BaselineWorkflowDefinitionId")
.HasColumnType("nvarchar(max)");
b.Property<string>("ConversationId")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<DateTimeOffset>("CreatedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("CreatedBy")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("GraphDiff")
.HasColumnType("nvarchar(max)");
b.Property<string>("Kind")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Rationale")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<DateTimeOffset?>("ReviewedAt")
.HasColumnType("datetimeoffset");
b.Property<string>("ReviewedBy")
.HasColumnType("nvarchar(max)");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("nvarchar(450)");
b.Property<string>("TenantId")
.HasColumnType("nvarchar(450)");
b.Property<string>("ValidationDiagnostics")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("Warnings")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.Property<string>("WorkflowPayload")
.IsRequired()
.HasColumnType("nvarchar(max)");
b.HasKey("Id");
b.HasIndex("Status");
b.HasIndex("TenantId", "ConversationId");
b.ToTable("AIProposals", "Elsa");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,27 @@
using System.Reflection;
using CShells.Features;
using Elsa.AI.Host.ShellFeatures;
using Elsa.AI.Persistence.EFCore.ShellFeatures;
using Elsa.Persistence.EFCore;
using Elsa.Persistence.EFCore.Extensions;
using Elsa.Platform.PackageManifest.Generator.Hints;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
namespace Elsa.AI.Persistence.EFCore.SqlServer.ShellFeatures;
[ManifestFeatureCategory("AI")]
[ManifestFeatureCategory("Persistence")]
[ShellFeature(
DisplayName = "SQL Server AI Persistence",
Description = "Provides SQL Server persistence for Weaver AI conversations, proposals and audit records",
DependsOn = [typeof(AIFeature)])]
[UsedImplicitly]
[ManifestInfrastructure("sqlserver-database", "database", Reason = "Stores Weaver AI persistence data in SQL Server.", Providers = new[] { "SQL Server" }, ConfigurationKeys = new[] { "ConnectionString" })]
public class SqlServerAIPersistenceShellFeature : EFCoreAIPersistenceShellFeatureBase
{
protected override void ConfigureProvider(DbContextOptionsBuilder builder, Assembly migrationsAssembly, string connectionString, ElsaDbContextOptions? options)
{
builder.UseElsaSqlServer(migrationsAssembly, connectionString, options);
}
}

View file

@ -0,0 +1,23 @@
using Elsa.Persistence.EFCore.Abstractions;
using Elsa.Persistence.EFCore.Extensions;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.AI.Persistence.EFCore.Sqlite;
[UsedImplicitly]
public class AIDbContextFactory : SqliteAIDesignTimeDbContextFactory<AIDbContext>;
public class SqliteAIDesignTimeDbContextFactory<TDbContext> : DesignTimeDbContextFactoryBase<TDbContext> where TDbContext : DbContext
{
protected override void ConfigureServices(IServiceCollection services)
{
services.AddSqliteEntityModelCreatingHandlers();
}
protected override void ConfigureBuilder(DbContextOptionsBuilder<TDbContext> builder, string connectionString)
{
builder.UseElsaSqlite(GetType().Assembly, connectionString);
}
}

View file

@ -0,0 +1,17 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Description>
Provides SQLite EF Core migrations for Elsa AI persistence.
</Description>
<PackageTags>elsa module ai persistence efcore sqlite</PackageTags>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" PrivateAssets="all" />
<ProjectReference Include="..\Elsa.Persistence.EFCore.Sqlite\Elsa.Persistence.EFCore.Sqlite.csproj" />
<ProjectReference Include="..\Elsa.AI.Host\Elsa.AI.Host.csproj" />
<ProjectReference Include="..\Elsa.AI.Persistence.EFCore\Elsa.AI.Persistence.EFCore.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,31 @@
using System.Reflection;
using Elsa.AI.Persistence.EFCore.Features;
using Elsa.Persistence.EFCore;
using Elsa.Persistence.EFCore.Extensions;
using Microsoft.EntityFrameworkCore.Infrastructure;
namespace Elsa.AI.Persistence.EFCore.Sqlite.Extensions;
public static class SqliteAIPersistenceFeatureExtensions
{
private static Assembly Assembly => typeof(SqliteAIPersistenceFeatureExtensions).Assembly;
public static AIPersistenceFeature UseSqlite(
this AIPersistenceFeature feature,
string? connectionString = null,
ElsaDbContextOptions? options = null,
Action<SqliteDbContextOptionsBuilder>? configure = null)
{
connectionString ??= "Data Source=elsa.sqlite.db;Cache=Shared;";
return feature.UseSqlite(_ => connectionString, options, configure);
}
public static AIPersistenceFeature UseSqlite(
this AIPersistenceFeature feature,
Func<IServiceProvider, string> connectionStringFunc,
ElsaDbContextOptions? options = null,
Action<SqliteDbContextOptionsBuilder>? configure = null)
{
return feature.UseSqlite(Assembly, connectionStringFunc, options, configure);
}
}

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

View file

@ -0,0 +1,207 @@
// <auto-generated />
using Elsa.AI.Persistence.EFCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Elsa.AI.Persistence.EFCore.Sqlite.Migrations.AI
{
[DbContext(typeof(AIDbContext))]
[Migration("20260608124921_Initial")]
partial class Initial
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.16");
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIAuditRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ActorId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ConversationId")
.HasColumnType("TEXT");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ProposalId")
.HasColumnType("TEXT");
b.Property<string>("Summary")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("TenantId")
.HasColumnType("TEXT");
b.Property<string>("Timestamp")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ToolInvocationId")
.HasColumnType("TEXT");
b.Property<string>("TraceId")
.HasColumnType("TEXT");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ActorId");
b.HasIndex("ProposalId");
b.HasIndex("ToolInvocationId");
b.HasIndex("TenantId", "ConversationId");
b.HasIndex("TenantId", "Timestamp");
b.ToTable("AIAuditRecords", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIConversationRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("CreatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Messages")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ProviderSessionId")
.HasColumnType("TEXT");
b.Property<string>("RetentionExpiresAt")
.HasColumnType("TEXT");
b.Property<string>("RetentionMode")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("TenantId")
.HasColumnType("TEXT");
b.Property<string>("Title")
.HasColumnType("TEXT");
b.Property<string>("UpdatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("RetentionExpiresAt");
b.HasIndex("Status");
b.HasIndex("TenantId", "UserId");
b.ToTable("AIConversations", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIProposalRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("AppliedAt")
.HasColumnType("TEXT");
b.Property<string>("AppliedBy")
.HasColumnType("TEXT");
b.Property<string>("BaselineVersionId")
.HasColumnType("TEXT");
b.Property<string>("BaselineWorkflowDefinitionId")
.HasColumnType("TEXT");
b.Property<string>("ConversationId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("CreatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("CreatedBy")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("GraphDiff")
.HasColumnType("TEXT");
b.Property<string>("Kind")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Rationale")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ReviewedAt")
.HasColumnType("TEXT");
b.Property<string>("ReviewedBy")
.HasColumnType("TEXT");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("TenantId")
.HasColumnType("TEXT");
b.Property<string>("ValidationDiagnostics")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Warnings")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("WorkflowPayload")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Status");
b.HasIndex("TenantId", "ConversationId");
b.ToTable("AIProposals", "Elsa");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,174 @@
using System;
using Elsa.Persistence.EFCore;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Elsa.AI.Persistence.EFCore.Sqlite.Migrations.AI
{
/// <inheritdoc />
public partial class Initial : Migration
{
private readonly IElsaDbContextSchema _schema;
public Initial(IElsaDbContextSchema schema)
{
_schema = schema ?? throw new ArgumentNullException(nameof(schema));
}
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.EnsureSchema(
name: _schema.Schema);
migrationBuilder.CreateTable(
name: "AIAuditRecords",
schema: _schema.Schema,
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
TenantId = table.Column<string>(type: "TEXT", nullable: true),
ActorId = table.Column<string>(type: "TEXT", nullable: false),
ConversationId = table.Column<string>(type: "TEXT", nullable: true),
ProposalId = table.Column<string>(type: "TEXT", nullable: true),
ToolInvocationId = table.Column<string>(type: "TEXT", nullable: true),
Type = table.Column<string>(type: "TEXT", nullable: false),
Timestamp = table.Column<string>(type: "TEXT", nullable: false),
TraceId = table.Column<string>(type: "TEXT", nullable: true),
Summary = table.Column<string>(type: "TEXT", nullable: false),
Data = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AIAuditRecords", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AIConversations",
schema: _schema.Schema,
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
TenantId = table.Column<string>(type: "TEXT", nullable: true),
UserId = table.Column<string>(type: "TEXT", nullable: false),
Title = table.Column<string>(type: "TEXT", nullable: true),
Status = table.Column<string>(type: "TEXT", nullable: false),
CreatedAt = table.Column<string>(type: "TEXT", nullable: false),
UpdatedAt = table.Column<string>(type: "TEXT", nullable: false),
ProviderSessionId = table.Column<string>(type: "TEXT", nullable: true),
RetentionMode = table.Column<string>(type: "TEXT", nullable: false),
RetentionExpiresAt = table.Column<string>(type: "TEXT", nullable: true),
Messages = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AIConversations", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AIProposals",
schema: _schema.Schema,
columns: table => new
{
Id = table.Column<string>(type: "TEXT", nullable: false),
TenantId = table.Column<string>(type: "TEXT", nullable: true),
ConversationId = table.Column<string>(type: "TEXT", nullable: false),
Kind = table.Column<string>(type: "TEXT", nullable: false),
Status = table.Column<string>(type: "TEXT", nullable: false),
BaselineWorkflowDefinitionId = table.Column<string>(type: "TEXT", nullable: true),
BaselineVersionId = table.Column<string>(type: "TEXT", nullable: true),
WorkflowPayload = table.Column<string>(type: "TEXT", nullable: false),
Rationale = table.Column<string>(type: "TEXT", nullable: false),
Warnings = table.Column<string>(type: "TEXT", nullable: false),
ValidationDiagnostics = table.Column<string>(type: "TEXT", nullable: false),
GraphDiff = table.Column<string>(type: "TEXT", nullable: true),
CreatedBy = table.Column<string>(type: "TEXT", nullable: false),
CreatedAt = table.Column<string>(type: "TEXT", nullable: false),
ReviewedBy = table.Column<string>(type: "TEXT", nullable: true),
ReviewedAt = table.Column<string>(type: "TEXT", nullable: true),
AppliedBy = table.Column<string>(type: "TEXT", nullable: true),
AppliedAt = table.Column<string>(type: "TEXT", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AIProposals", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_ActorId",
schema: _schema.Schema,
table: "AIAuditRecords",
column: "ActorId");
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_ProposalId",
schema: _schema.Schema,
table: "AIAuditRecords",
column: "ProposalId");
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_TenantId_ConversationId",
schema: _schema.Schema,
table: "AIAuditRecords",
columns: new[] { "TenantId", "ConversationId" });
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_TenantId_Timestamp",
schema: _schema.Schema,
table: "AIAuditRecords",
columns: new[] { "TenantId", "Timestamp" });
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_ToolInvocationId",
schema: _schema.Schema,
table: "AIAuditRecords",
column: "ToolInvocationId");
migrationBuilder.CreateIndex(
name: "IX_AIConversations_RetentionExpiresAt",
schema: _schema.Schema,
table: "AIConversations",
column: "RetentionExpiresAt");
migrationBuilder.CreateIndex(
name: "IX_AIConversations_Status",
schema: _schema.Schema,
table: "AIConversations",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_AIConversations_TenantId_UserId",
schema: _schema.Schema,
table: "AIConversations",
columns: new[] { "TenantId", "UserId" });
migrationBuilder.CreateIndex(
name: "IX_AIProposals_Status",
schema: _schema.Schema,
table: "AIProposals",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_AIProposals_TenantId_ConversationId",
schema: _schema.Schema,
table: "AIProposals",
columns: new[] { "TenantId", "ConversationId" });
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "AIAuditRecords",
schema: _schema.Schema);
migrationBuilder.DropTable(
name: "AIConversations",
schema: _schema.Schema);
migrationBuilder.DropTable(
name: "AIProposals",
schema: _schema.Schema);
}
}
}

View file

@ -0,0 +1,204 @@
// <auto-generated />
using Elsa.AI.Persistence.EFCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace Elsa.AI.Persistence.EFCore.Sqlite.Migrations.AI
{
[DbContext(typeof(AIDbContext))]
partial class AIDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasDefaultSchema("Elsa")
.HasAnnotation("ProductVersion", "9.0.16");
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIAuditRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("ActorId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ConversationId")
.HasColumnType("TEXT");
b.Property<string>("Data")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ProposalId")
.HasColumnType("TEXT");
b.Property<string>("Summary")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("TenantId")
.HasColumnType("TEXT");
b.Property<string>("Timestamp")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ToolInvocationId")
.HasColumnType("TEXT");
b.Property<string>("TraceId")
.HasColumnType("TEXT");
b.Property<string>("Type")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("ActorId");
b.HasIndex("ProposalId");
b.HasIndex("ToolInvocationId");
b.HasIndex("TenantId", "ConversationId");
b.HasIndex("TenantId", "Timestamp");
b.ToTable("AIAuditRecords", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIConversationRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("CreatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Messages")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ProviderSessionId")
.HasColumnType("TEXT");
b.Property<string>("RetentionExpiresAt")
.HasColumnType("TEXT");
b.Property<string>("RetentionMode")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("TenantId")
.HasColumnType("TEXT");
b.Property<string>("Title")
.HasColumnType("TEXT");
b.Property<string>("UpdatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UserId")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("RetentionExpiresAt");
b.HasIndex("Status");
b.HasIndex("TenantId", "UserId");
b.ToTable("AIConversations", "Elsa");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIProposalRecord", b =>
{
b.Property<string>("Id")
.HasColumnType("TEXT");
b.Property<string>("AppliedAt")
.HasColumnType("TEXT");
b.Property<string>("AppliedBy")
.HasColumnType("TEXT");
b.Property<string>("BaselineVersionId")
.HasColumnType("TEXT");
b.Property<string>("BaselineWorkflowDefinitionId")
.HasColumnType("TEXT");
b.Property<string>("ConversationId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("CreatedAt")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("CreatedBy")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("GraphDiff")
.HasColumnType("TEXT");
b.Property<string>("Kind")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Rationale")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ReviewedAt")
.HasColumnType("TEXT");
b.Property<string>("ReviewedBy")
.HasColumnType("TEXT");
b.Property<string>("Status")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("TenantId")
.HasColumnType("TEXT");
b.Property<string>("ValidationDiagnostics")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Warnings")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("WorkflowPayload")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Status");
b.HasIndex("TenantId", "ConversationId");
b.ToTable("AIProposals", "Elsa");
});
#pragma warning restore 612, 618
}
}
}

View file

@ -0,0 +1,34 @@
using System.Reflection;
using CShells.Features;
using Elsa.AI.Host.ShellFeatures;
using Elsa.AI.Persistence.EFCore.ShellFeatures;
using Elsa.Persistence.EFCore;
using Elsa.Persistence.EFCore.Extensions;
using Elsa.Platform.PackageManifest.Generator.Hints;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.AI.Persistence.EFCore.Sqlite.ShellFeatures;
[ManifestFeatureCategory("AI")]
[ManifestFeatureCategory("Persistence")]
[ShellFeature(
DisplayName = "SQLite AI Persistence",
Description = "Provides SQLite persistence for Weaver AI conversations, proposals and audit records",
DependsOn = [typeof(AIFeature)])]
[UsedImplicitly]
[ManifestInfrastructure("sqlite-database", "database", Reason = "Stores Weaver AI persistence data in SQLite.", Providers = new[] { "SQLite" }, ConfigurationKeys = new[] { "ConnectionString" })]
public class SqliteAIPersistenceShellFeature : EFCoreAIPersistenceShellFeatureBase
{
protected override void ConfigureProvider(DbContextOptionsBuilder builder, Assembly migrationsAssembly, string connectionString, ElsaDbContextOptions? options)
{
builder.UseElsaSqlite(migrationsAssembly, connectionString, options);
}
protected override void OnConfiguring(IServiceCollection services)
{
services.AddSqliteEntityModelCreatingHandlers();
base.OnConfiguring(services);
}
}

View file

@ -1,9 +1,21 @@
using Elsa.AI.Persistence.EFCore.Entities;
using Elsa.Persistence.EFCore;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.AI.Persistence.EFCore;
public class AIDbContext(DbContextOptions<AIDbContext> options) : DbContext(options)
public class AIDbContext : ElsaDbContextBase
{
private static readonly IServiceProvider EmptyServiceProvider = new ServiceCollection().BuildServiceProvider();
public AIDbContext(DbContextOptions<AIDbContext> options) : this(options, EmptyServiceProvider)
{
}
public AIDbContext(DbContextOptions<AIDbContext> options, IServiceProvider serviceProvider) : base(options, serviceProvider)
{
}
public DbSet<AIConversationRecord> Conversations => Set<AIConversationRecord>();
public DbSet<AIProposalRecord> Proposals => Set<AIProposalRecord>();
public DbSet<AIAuditRecord> AuditRecords => Set<AIAuditRecord>();
@ -46,5 +58,7 @@ public class AIDbContext(DbContextOptions<AIDbContext> options) : DbContext(opti
entity.Property(x => x.RetentionMode).IsRequired();
entity.Property(x => x.Messages).IsRequired();
});
base.OnModelCreating(modelBuilder);
}
}

View file

@ -11,11 +11,11 @@
<PackageReference Include="CShells" />
<PackageReference Include="Microsoft.EntityFrameworkCore" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" PrivateAssets="all" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Elsa.AI.Abstractions\Elsa.AI.Abstractions.csproj" />
<ProjectReference Include="..\Elsa.Persistence.EFCore.Common\Elsa.Persistence.EFCore.Common.csproj" />
<ProjectReference Include="..\..\common\Elsa.Features\Elsa.Features.csproj" />
</ItemGroup>

View file

@ -23,7 +23,13 @@ public static class ServiceCollectionExtensions
}
services.TryAddScoped<AIDbContext>();
services.AddAIPersistenceStoreServices();
return services;
}
internal static IServiceCollection AddAIPersistenceStoreServices(this IServiceCollection services)
{
services.Replace(ServiceDescriptor.Scoped<IAIConversationStore, EFCoreAIConversationStore>());
services.Replace(ServiceDescriptor.Scoped<IAIProposalStore, EFCoreAIProposalStore>());
services.TryAddEnumerable(ServiceDescriptor.Scoped<IAIAuditEventHandler, EFCoreAIAuditSink>());

View file

@ -1,16 +1,22 @@
using Elsa.Extensions;
using Elsa.Features.Abstractions;
using Elsa.Features.Services;
using Elsa.Persistence.EFCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.AI.Persistence.EFCore.Features;
public class AIPersistenceFeature(IModule module) : FeatureBase(module)
public class AIPersistenceFeature(IModule module) : PersistenceFeatureBase<AIPersistenceFeature, AIDbContext>(module)
{
public Action<DbContextOptionsBuilder>? ConfigureDbContext { get; set; }
public override void Apply()
{
Services.AddAIPersistenceStores(ConfigureDbContext);
if (DbContextOptionsBuilder == null && ConfigureDbContext != null)
DbContextOptionsBuilder = (_, builder) => ConfigureDbContext(builder);
base.Apply();
Services.AddScoped(sp => sp.GetRequiredService<IDbContextFactory<AIDbContext>>().CreateDbContext());
Services.AddAIPersistenceStoreServices();
}
}

View file

@ -1,158 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Elsa.AI.Persistence.EFCore.Migrations;
[DbContext(typeof(AIDbContext))]
[Migration("20260521002000_Initial")]
partial class Initial
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.16");
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIAuditRecord", b =>
{
b.Property<string>("Id");
b.Property<string>("ActorId")
.IsRequired();
b.Property<string>("ConversationId");
b.Property<string>("Data")
.IsRequired();
b.Property<string>("ProposalId");
b.Property<string>("Summary")
.IsRequired();
b.Property<string>("TenantId");
b.Property<DateTimeOffset>("Timestamp");
b.Property<string>("ToolInvocationId");
b.Property<string>("TraceId");
b.Property<string>("Type")
.IsRequired();
b.HasKey("Id");
b.HasIndex("ActorId");
b.HasIndex("ProposalId");
b.HasIndex("ToolInvocationId");
b.HasIndex("TenantId", "ConversationId");
b.HasIndex("TenantId", "Timestamp");
b.ToTable("AIAuditRecords");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIConversationRecord", b =>
{
b.Property<string>("Id");
b.Property<DateTimeOffset>("CreatedAt");
b.Property<string>("Messages")
.IsRequired();
b.Property<string>("ProviderSessionId");
b.Property<DateTimeOffset?>("RetentionExpiresAt");
b.Property<string>("RetentionMode")
.IsRequired();
b.Property<string>("Status")
.IsRequired();
b.Property<string>("TenantId");
b.Property<string>("Title");
b.Property<DateTimeOffset>("UpdatedAt");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RetentionExpiresAt");
b.HasIndex("Status");
b.HasIndex("TenantId", "UserId");
b.ToTable("AIConversations");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIProposalRecord", b =>
{
b.Property<string>("Id");
b.Property<string>("AppliedBy");
b.Property<DateTimeOffset?>("AppliedAt");
b.Property<string>("BaselineVersionId");
b.Property<string>("BaselineWorkflowDefinitionId");
b.Property<string>("ConversationId")
.IsRequired();
b.Property<DateTimeOffset>("CreatedAt");
b.Property<string>("CreatedBy")
.IsRequired();
b.Property<string>("GraphDiff");
b.Property<string>("Kind")
.IsRequired();
b.Property<string>("Rationale")
.IsRequired();
b.Property<string>("ReviewedBy");
b.Property<DateTimeOffset?>("ReviewedAt");
b.Property<string>("Status")
.IsRequired();
b.Property<string>("TenantId");
b.Property<string>("ValidationDiagnostics")
.IsRequired();
b.Property<string>("Warnings")
.IsRequired();
b.Property<string>("WorkflowPayload")
.IsRequired();
b.HasKey("Id");
b.HasIndex("Status");
b.HasIndex("TenantId", "ConversationId");
b.ToTable("AIProposals");
});
#pragma warning restore 612, 618
}
}

View file

@ -1,138 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Elsa.AI.Persistence.EFCore.Migrations;
public partial class Initial : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "AIAuditRecords",
columns: table => new
{
Id = table.Column<string>(nullable: false),
TenantId = table.Column<string>(nullable: true),
ActorId = table.Column<string>(nullable: false),
ConversationId = table.Column<string>(nullable: true),
ProposalId = table.Column<string>(nullable: true),
ToolInvocationId = table.Column<string>(nullable: true),
Type = table.Column<string>(nullable: false),
Timestamp = table.Column<DateTimeOffset>(nullable: false),
TraceId = table.Column<string>(nullable: true),
Summary = table.Column<string>(nullable: false),
Data = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AIAuditRecords", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AIConversations",
columns: table => new
{
Id = table.Column<string>(nullable: false),
TenantId = table.Column<string>(nullable: true),
UserId = table.Column<string>(nullable: false),
Title = table.Column<string>(nullable: true),
Status = table.Column<string>(nullable: false),
CreatedAt = table.Column<DateTimeOffset>(nullable: false),
UpdatedAt = table.Column<DateTimeOffset>(nullable: false),
ProviderSessionId = table.Column<string>(nullable: true),
RetentionMode = table.Column<string>(nullable: false),
RetentionExpiresAt = table.Column<DateTimeOffset>(nullable: true),
Messages = table.Column<string>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_AIConversations", x => x.Id);
});
migrationBuilder.CreateTable(
name: "AIProposals",
columns: table => new
{
Id = table.Column<string>(nullable: false),
TenantId = table.Column<string>(nullable: true),
ConversationId = table.Column<string>(nullable: false),
Kind = table.Column<string>(nullable: false),
Status = table.Column<string>(nullable: false),
BaselineWorkflowDefinitionId = table.Column<string>(nullable: true),
BaselineVersionId = table.Column<string>(nullable: true),
WorkflowPayload = table.Column<string>(nullable: false),
Rationale = table.Column<string>(nullable: false),
Warnings = table.Column<string>(nullable: false),
ValidationDiagnostics = table.Column<string>(nullable: false),
GraphDiff = table.Column<string>(nullable: true),
CreatedBy = table.Column<string>(nullable: false),
CreatedAt = table.Column<DateTimeOffset>(nullable: false),
ReviewedBy = table.Column<string>(nullable: true),
ReviewedAt = table.Column<DateTimeOffset>(nullable: true),
AppliedBy = table.Column<string>(nullable: true),
AppliedAt = table.Column<DateTimeOffset>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_AIProposals", x => x.Id);
});
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_ProposalId",
table: "AIAuditRecords",
column: "ProposalId");
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_ActorId",
table: "AIAuditRecords",
column: "ActorId");
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_TenantId_Timestamp",
table: "AIAuditRecords",
columns: new[] { "TenantId", "Timestamp" });
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_TenantId_ConversationId",
table: "AIAuditRecords",
columns: new[] { "TenantId", "ConversationId" });
migrationBuilder.CreateIndex(
name: "IX_AIAuditRecords_ToolInvocationId",
table: "AIAuditRecords",
column: "ToolInvocationId");
migrationBuilder.CreateIndex(
name: "IX_AIConversations_RetentionExpiresAt",
table: "AIConversations",
column: "RetentionExpiresAt");
migrationBuilder.CreateIndex(
name: "IX_AIConversations_Status",
table: "AIConversations",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_AIConversations_TenantId_UserId",
table: "AIConversations",
columns: new[] { "TenantId", "UserId" });
migrationBuilder.CreateIndex(
name: "IX_AIProposals_Status",
table: "AIProposals",
column: "Status");
migrationBuilder.CreateIndex(
name: "IX_AIProposals_TenantId_ConversationId",
table: "AIProposals",
columns: new[] { "TenantId", "ConversationId" });
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(name: "AIAuditRecords");
migrationBuilder.DropTable(name: "AIConversations");
migrationBuilder.DropTable(name: "AIProposals");
}
}

View file

@ -1,156 +0,0 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
#nullable disable
namespace Elsa.AI.Persistence.EFCore.Migrations;
[DbContext(typeof(AIDbContext))]
partial class AIDbContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.16");
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIAuditRecord", b =>
{
b.Property<string>("Id");
b.Property<string>("ActorId")
.IsRequired();
b.Property<string>("ConversationId");
b.Property<string>("Data")
.IsRequired();
b.Property<string>("ProposalId");
b.Property<string>("Summary")
.IsRequired();
b.Property<string>("TenantId");
b.Property<DateTimeOffset>("Timestamp");
b.Property<string>("ToolInvocationId");
b.Property<string>("TraceId");
b.Property<string>("Type")
.IsRequired();
b.HasKey("Id");
b.HasIndex("ActorId");
b.HasIndex("ProposalId");
b.HasIndex("ToolInvocationId");
b.HasIndex("TenantId", "ConversationId");
b.HasIndex("TenantId", "Timestamp");
b.ToTable("AIAuditRecords");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIConversationRecord", b =>
{
b.Property<string>("Id");
b.Property<DateTimeOffset>("CreatedAt");
b.Property<string>("Messages")
.IsRequired();
b.Property<string>("ProviderSessionId");
b.Property<DateTimeOffset?>("RetentionExpiresAt");
b.Property<string>("RetentionMode")
.IsRequired();
b.Property<string>("Status")
.IsRequired();
b.Property<string>("TenantId");
b.Property<string>("Title");
b.Property<DateTimeOffset>("UpdatedAt");
b.Property<string>("UserId")
.IsRequired();
b.HasKey("Id");
b.HasIndex("RetentionExpiresAt");
b.HasIndex("Status");
b.HasIndex("TenantId", "UserId");
b.ToTable("AIConversations");
});
modelBuilder.Entity("Elsa.AI.Persistence.EFCore.Entities.AIProposalRecord", b =>
{
b.Property<string>("Id");
b.Property<string>("AppliedBy");
b.Property<DateTimeOffset?>("AppliedAt");
b.Property<string>("BaselineVersionId");
b.Property<string>("BaselineWorkflowDefinitionId");
b.Property<string>("ConversationId")
.IsRequired();
b.Property<DateTimeOffset>("CreatedAt");
b.Property<string>("CreatedBy")
.IsRequired();
b.Property<string>("GraphDiff");
b.Property<string>("Kind")
.IsRequired();
b.Property<string>("Rationale")
.IsRequired();
b.Property<string>("ReviewedBy");
b.Property<DateTimeOffset?>("ReviewedAt");
b.Property<string>("Status")
.IsRequired();
b.Property<string>("TenantId");
b.Property<string>("ValidationDiagnostics")
.IsRequired();
b.Property<string>("Warnings")
.IsRequired();
b.Property<string>("WorkflowPayload")
.IsRequired();
b.HasKey("Id");
b.HasIndex("Status");
b.HasIndex("TenantId", "ConversationId");
b.ToTable("AIProposals");
});
#pragma warning restore 612, 618
}
}

View file

@ -1,25 +0,0 @@
using CShells.Features;
using Elsa.Extensions;
using Elsa.Platform.PackageManifest.Generator.Hints;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.AI.Persistence.EFCore.ShellFeatures;
[ManifestFeatureCategory("AI")]
[ManifestFeatureCategory("Persistence")]
[ShellFeature(
"AIPersistence",
DisplayName = "AI Persistence",
Description = "Registers durable EF Core stores for Weaver AI conversations, proposals and audit records")]
[UsedImplicitly]
public class AIPersistenceFeature : IShellFeature
{
public Action<DbContextOptionsBuilder>? ConfigureDbContext { get; set; }
public void ConfigureServices(IServiceCollection services)
{
services.AddAIPersistenceStores(ConfigureDbContext);
}
}

View file

@ -0,0 +1,19 @@
using Elsa.Extensions;
using Elsa.Persistence.EFCore;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
namespace Elsa.AI.Persistence.EFCore.ShellFeatures;
/// <summary>
/// Base class for provider-specific AI persistence shell features.
/// </summary>
public abstract class EFCoreAIPersistenceShellFeatureBase : PersistenceShellFeatureBase<AIDbContext>
{
/// <inheritdoc />
protected override void OnConfiguring(IServiceCollection services)
{
services.AddScoped(sp => sp.GetRequiredService<IDbContextFactory<AIDbContext>>().CreateDbContext());
services.AddAIPersistenceStoreServices();
}
}

View file

@ -0,0 +1,15 @@
using Elsa.AI.Persistence.EFCore.Sqlite;
using Elsa.Persistence.EFCore;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
namespace Elsa.AI.Persistence.EFCore.UnitTests;
internal static class AIDbContextOptionsBuilderExtensions
{
public static DbContextOptionsBuilder<AIDbContext> UseSqliteAIMigrations(this DbContextOptionsBuilder<AIDbContext> builder, SqliteConnection connection)
{
builder.UseElsaDbContextOptions(null);
return builder.UseSqlite(connection, db => db.MigrationsAssembly(typeof(AIDbContextFactory).Assembly.GetName().Name));
}
}

View file

@ -102,7 +102,7 @@ public class EFCoreAIAuditSinkTests : IAsyncLifetime
public async Task InitializeAsync()
{
await _connection.OpenAsync();
_dbContext = new AIDbContext(new DbContextOptionsBuilder<AIDbContext>().UseSqlite(_connection).Options);
_dbContext = new AIDbContext(new DbContextOptionsBuilder<AIDbContext>().UseSqliteAIMigrations(_connection).Options);
await _dbContext.Database.MigrateAsync();
}

View file

@ -499,7 +499,7 @@ public class EFCoreAIConversationStoreTests : IAsyncLifetime
public async Task InitializeAsync()
{
await _connection.OpenAsync();
_dbContext = new AIDbContext(new DbContextOptionsBuilder<AIDbContext>().UseSqlite(_connection).Options);
_dbContext = new AIDbContext(new DbContextOptionsBuilder<AIDbContext>().UseSqliteAIMigrations(_connection).Options);
await _dbContext.Database.MigrateAsync();
}

View file

@ -209,7 +209,7 @@ public class EFCoreAIProposalStoreTests : IAsyncLifetime
public async Task ProposalStoreRetriesConcurrentInsertsAsUpdates()
{
var options = new DbContextOptionsBuilder<AIDbContext>()
.UseSqlite(_connection)
.UseSqliteAIMigrations(_connection)
.AddInterceptors(new ConcurrentProposalInsertInterceptor(_connection))
.Options;
await using var context = new AIDbContext(options);
@ -344,7 +344,7 @@ public class EFCoreAIProposalStoreTests : IAsyncLifetime
public async Task InitializeAsync()
{
await _connection.OpenAsync();
_dbContext = new AIDbContext(new DbContextOptionsBuilder<AIDbContext>().UseSqlite(_connection).Options);
_dbContext = new AIDbContext(new DbContextOptionsBuilder<AIDbContext>().UseSqliteAIMigrations(_connection).Options);
await _dbContext.Database.MigrateAsync();
}
@ -368,7 +368,7 @@ public class EFCoreAIProposalStoreTests : IAsyncLifetime
return result;
_inserted = true;
var options = new DbContextOptionsBuilder<AIDbContext>().UseSqlite(connection).Options;
var options = new DbContextOptionsBuilder<AIDbContext>().UseSqliteAIMigrations(connection).Options;
await using var competingContext = new AIDbContext(options);
competingContext.Proposals.Add(new AIProposalRecord
{

View file

@ -7,6 +7,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" />
<ProjectReference Include="..\..\..\src\modules\Elsa.AI.Persistence.EFCore.Sqlite\Elsa.AI.Persistence.EFCore.Sqlite.csproj" />
<ProjectReference Include="..\..\..\src\modules\Elsa.AI.Persistence.EFCore\Elsa.AI.Persistence.EFCore.csproj" />
<ProjectReference Include="..\..\..\src\common\Elsa.Testing.Shared\Elsa.Testing.Shared.csproj" />
</ItemGroup>