Skip to content

Commit 398dd70

Browse files
authored
Adds Aspire Oracle EntityFrameworkCore Database component. (#1295)
* Adds Aspire Oracle EntityFrameworkCore Database component. * AppModel apis * tests for oracle hosting * adjusting to reviews * Oracle has a 30 character password limit * functional test * adjusting to reviews * configurationschema was outdated * adjusting to reviews * updated with the latest bits
1 parent 9d4bd62 commit 398dd70

29 files changed

+1311
-2
lines changed

Aspire.sln

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.MongoDB.Driver.Tests
172172
EndProject
173173
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConfigurationSchemaGenerator.Tests", "tests\ConfigurationSchemaGenerator.Tests\ConfigurationSchemaGenerator.Tests.csproj", "{00FEA181-84C9-42A7-AC81-29A9F176A1A0}"
174174
EndProject
175+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Oracle.EntityFrameworkCore", "src\Components\Aspire.Oracle.EntityFrameworkCore\Aspire.Oracle.EntityFrameworkCore.csproj", "{A778F29A-6C40-4C53-A793-F23F20679ADE}"
176+
EndProject
177+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Aspire.Oracle.EntityFrameworkCore.Tests", "tests\Aspire.Oracle.EntityFrameworkCore.Tests\Aspire.Oracle.EntityFrameworkCore.Tests.csproj", "{A331C123-35A5-4E81-9999-354159821374}"
178+
EndProject
175179
Global
176180
GlobalSection(SolutionConfigurationPlatforms) = preSolution
177181
Debug|Any CPU = Debug|Any CPU
@@ -458,6 +462,14 @@ Global
458462
{00FEA181-84C9-42A7-AC81-29A9F176A1A0}.Debug|Any CPU.Build.0 = Debug|Any CPU
459463
{00FEA181-84C9-42A7-AC81-29A9F176A1A0}.Release|Any CPU.ActiveCfg = Release|Any CPU
460464
{00FEA181-84C9-42A7-AC81-29A9F176A1A0}.Release|Any CPU.Build.0 = Release|Any CPU
465+
{A778F29A-6C40-4C53-A793-F23F20679ADE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
466+
{A778F29A-6C40-4C53-A793-F23F20679ADE}.Debug|Any CPU.Build.0 = Debug|Any CPU
467+
{A778F29A-6C40-4C53-A793-F23F20679ADE}.Release|Any CPU.ActiveCfg = Release|Any CPU
468+
{A778F29A-6C40-4C53-A793-F23F20679ADE}.Release|Any CPU.Build.0 = Release|Any CPU
469+
{A331C123-35A5-4E81-9999-354159821374}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
470+
{A331C123-35A5-4E81-9999-354159821374}.Debug|Any CPU.Build.0 = Debug|Any CPU
471+
{A331C123-35A5-4E81-9999-354159821374}.Release|Any CPU.ActiveCfg = Release|Any CPU
472+
{A331C123-35A5-4E81-9999-354159821374}.Release|Any CPU.Build.0 = Release|Any CPU
461473
EndGlobalSection
462474
GlobalSection(SolutionProperties) = preSolution
463475
HideSolutionNode = FALSE
@@ -537,6 +549,8 @@ Global
537549
{20A5A907-A135-4735-B4BF-E13514F360E3} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2}
538550
{E592E447-BA3C-44FA-86C1-EBEDC864A644} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
539551
{00FEA181-84C9-42A7-AC81-29A9F176A1A0} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
552+
{A778F29A-6C40-4C53-A793-F23F20679ADE} = {27381127-6C45-4B4C-8F18-41FF48DFE4B2}
553+
{A331C123-35A5-4E81-9999-354159821374} = {4981B3A5-4AFD-4191-BF7D-8692D9783D60}
540554
EndGlobalSection
541555
GlobalSection(ExtensibilityGlobals) = postSolution
542556
SolutionGuid = {6DCEDFEC-988E-4CB3-B45B-191EB5086E0C}

Directory.Packages.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@
7979
<PackageVersion Include="MySqlConnector.DependencyInjection" Version="2.3.1" />
8080
<PackageVersion Include="Npgsql.DependencyInjection" Version="8.0.0" />
8181
<PackageVersion Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="8.0.0" />
82+
<PackageVersion Include="Oracle.EntityFrameworkCore" Version="8.21.121" />
8283
<PackageVersion Include="Polly" Version="8.2.0" />
8384
<PackageVersion Include="RabbitMQ.Client" Version="6.7.0" />
8485
<PackageVersion Include="StackExchange.Redis" Version="2.7.4" />
@@ -105,4 +106,4 @@
105106
<PackageVersion Include="Microsoft.Signed.Wix" Version="1.0.0-v3.14.0.5722" />
106107
<PackageVersion Include="Microsoft.DotNet.Build.Tasks.Installers" Version="8.0.0-beta.23564.4" />
107108
</ItemGroup>
108-
</Project>
109+
</Project>
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Aspire.Hosting.ApplicationModel;
5+
6+
/// <summary>
7+
/// Represents a Oracle Database resource that requires a connection string.
8+
/// </summary>
9+
public interface IOracleDatabaseParentResource : IResourceWithConnectionString, IResourceWithEnvironment
10+
{
11+
}
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System.Net.Sockets;
5+
using Aspire.Hosting.ApplicationModel;
6+
using Aspire.Hosting.Publishing;
7+
8+
namespace Aspire.Hosting;
9+
10+
/// <summary>
11+
/// Provides extension methods for adding Oracle Database resources to an <see cref="IDistributedApplicationBuilder"/>.
12+
/// </summary>
13+
public static class OracleDatabaseBuilderExtensions
14+
{
15+
private const string PasswordEnvVarName = "ORACLE_PWD";
16+
17+
/// <summary>
18+
/// Adds a Oracle Database container to the application model. The default image is "database/free" and the tag is "latest".
19+
/// </summary>
20+
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
21+
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
22+
/// <param name="port">The host port for Oracle Database.</param>
23+
/// <param name="password">The password for the Oracle Database container. Defaults to a random password.</param>
24+
/// <returns>A reference to the <see cref="IResourceBuilder{OracleDatabaseContainerResource}"/>.</returns>
25+
public static IResourceBuilder<OracleDatabaseContainerResource> AddOracleDatabaseContainer(this IDistributedApplicationBuilder builder, string name, int? port = null, string? password = null)
26+
{
27+
password = password ?? Guid.NewGuid().ToString("N").Substring(0, 30);
28+
var oracleDatabaseContainer = new OracleDatabaseContainerResource(name, password);
29+
return builder.AddResource(oracleDatabaseContainer)
30+
.WithManifestPublishingCallback(context => WriteOracleDatabaseContainerResourceToManifest(context, oracleDatabaseContainer))
31+
.WithAnnotation(new EndpointAnnotation(ProtocolType.Tcp, port: port, containerPort: 1521))
32+
.WithAnnotation(new ContainerImageAnnotation { Image = "database/free", Tag = "latest", Registry = "container-registry.oracle.com" })
33+
.WithEnvironment(context =>
34+
{
35+
if (context.PublisherName == "manifest")
36+
{
37+
context.EnvironmentVariables.Add(PasswordEnvVarName, $"{{{oracleDatabaseContainer.Name}.inputs.password}}");
38+
}
39+
else
40+
{
41+
context.EnvironmentVariables.Add(PasswordEnvVarName, oracleDatabaseContainer.Password);
42+
}
43+
});
44+
}
45+
46+
/// <summary>
47+
/// Adds a Oracle Database resource to the application model. A container is used for local development.
48+
/// </summary>
49+
/// <param name="builder">The <see cref="IDistributedApplicationBuilder"/>.</param>
50+
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
51+
/// <returns>A reference to the <see cref="IResourceBuilder{OracleDatabaseServerResource}"/>.</returns>
52+
public static IResourceBuilder<OracleDatabaseServerResource> AddOracleDatabase(this IDistributedApplicationBuilder builder, string name)
53+
{
54+
var password = Guid.NewGuid().ToString("N").Substring(0, 30);
55+
var oracleDatabaseServer = new OracleDatabaseServerResource(name, password);
56+
return builder.AddResource(oracleDatabaseServer)
57+
.WithManifestPublishingCallback(WriteOracleDatabaseContainerToManifest)
58+
.WithAnnotation(new EndpointAnnotation(ProtocolType.Tcp, containerPort: 1521))
59+
.WithAnnotation(new ContainerImageAnnotation { Image = "database/free", Tag = "latest", Registry = "container-registry.oracle.com" })
60+
.WithEnvironment(PasswordEnvVarName, () => oracleDatabaseServer.Password);
61+
}
62+
63+
/// <summary>
64+
/// Adds a Oracle Database database to the application model.
65+
/// </summary>
66+
/// <param name="builder">The Oracle Database server resource builder.</param>
67+
/// <param name="name">The name of the resource. This name will be used as the connection string name when referenced in a dependency.</param>
68+
/// <returns>A reference to the <see cref="IResourceBuilder{OracleDatabaseResource}"/>.</returns>
69+
public static IResourceBuilder<OracleDatabaseResource> AddDatabase(this IResourceBuilder<IOracleDatabaseParentResource> builder, string name)
70+
{
71+
var oracleDatabase = new OracleDatabaseResource(name, builder.Resource);
72+
return builder.ApplicationBuilder.AddResource(oracleDatabase)
73+
.WithManifestPublishingCallback(context => WriteOracleDatabaseToManifest(context, oracleDatabase));
74+
}
75+
76+
private static void WriteOracleDatabaseContainerToManifest(ManifestPublishingContext context)
77+
{
78+
context.Writer.WriteString("type", "oracle.server.v0");
79+
}
80+
81+
private static void WriteOracleDatabaseToManifest(ManifestPublishingContext context, OracleDatabaseResource oracleDatabase)
82+
{
83+
context.Writer.WriteString("type", "oracle.database.v0");
84+
context.Writer.WriteString("parent", oracleDatabase.Parent.Name);
85+
}
86+
87+
private static void WriteOracleDatabaseContainerResourceToManifest(ManifestPublishingContext context, OracleDatabaseContainerResource resource)
88+
{
89+
context.WriteContainer(resource);
90+
context.Writer.WriteString( // "connectionString": "...",
91+
"connectionString",
92+
$"user id=system;password={{{resource.Name}.inputs.password}};data source={{{resource.Name}.bindings.tcp.host}}:{{{resource.Name}.bindings.tcp.port}};");
93+
context.Writer.WriteStartObject("inputs"); // "inputs": {
94+
context.Writer.WriteStartObject("password"); // "password": {
95+
context.Writer.WriteString("type", "string"); // "type": "string",
96+
context.Writer.WriteBoolean("secret", true); // "secret": true,
97+
context.Writer.WriteStartObject("default"); // "default": {
98+
context.Writer.WriteStartObject("generate"); // "generate": {
99+
context.Writer.WriteNumber("minLength", 10); // "minLength": 10,
100+
context.Writer.WriteEndObject(); // }
101+
context.Writer.WriteEndObject(); // }
102+
context.Writer.WriteEndObject(); // }
103+
context.Writer.WriteEndObject(); // }
104+
}
105+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Aspire.Hosting.Utils;
5+
6+
namespace Aspire.Hosting.ApplicationModel;
7+
8+
/// <summary>
9+
/// A resource that represents a Oracle Database container.
10+
/// </summary>
11+
/// <param name="name">The name of the resource.</param>
12+
/// <param name="password">The Oracle Database server password.</param>
13+
public class OracleDatabaseContainerResource(string name, string password) : ContainerResource(name), IOracleDatabaseParentResource
14+
{
15+
public string Password { get; } = password;
16+
17+
/// <summary>
18+
/// Gets the connection string for the Oracle Database server.
19+
/// </summary>
20+
/// <returns>A connection string for the Oracle Database server in the form "user id=system;password=password;data source=localhost:port".</returns>
21+
public string? GetConnectionString()
22+
{
23+
if (!this.TryGetAllocatedEndPoints(out var allocatedEndpoints))
24+
{
25+
throw new DistributedApplicationException("Expected allocated endpoints!");
26+
}
27+
28+
var allocatedEndpoint = allocatedEndpoints.Single(); // We should only have one endpoint for Oracle Database.
29+
30+
var connectionString = $"user id=system;password={PasswordUtil.EscapePassword(Password)};data source={allocatedEndpoint.Address}:{allocatedEndpoint.Port}";
31+
return connectionString;
32+
}
33+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
namespace Aspire.Hosting.ApplicationModel;
5+
6+
/// <summary>
7+
/// A resource that represents a Oracle Database database. This is a child resource of a <see cref="OracleDatabaseContainerResource"/>.
8+
/// </summary>
9+
/// <param name="name">The name of the resource.</param>
10+
/// <param name="oracleParentResource">The Oracle Database parent resource associated with this database.</param>
11+
public class OracleDatabaseResource(string name, IOracleDatabaseParentResource oracleParentResource) : Resource(name), IResourceWithParent<IOracleDatabaseParentResource>, IResourceWithConnectionString
12+
{
13+
public IOracleDatabaseParentResource Parent { get; } = oracleParentResource;
14+
15+
/// <summary>
16+
/// Gets the connection string for the Oracle Database.
17+
/// </summary>
18+
/// <returns>A connection string for the Oracle Database.</returns>
19+
public string? GetConnectionString()
20+
{
21+
if (Parent.GetConnectionString() is { } connectionString)
22+
{
23+
return $"{connectionString}/{Name}";
24+
}
25+
else
26+
{
27+
throw new DistributedApplicationException("Parent resource connection string was null.");
28+
}
29+
}
30+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using Aspire.Hosting.Utils;
5+
6+
namespace Aspire.Hosting.ApplicationModel;
7+
8+
/// <summary>
9+
/// A resource that represents a Oracle Database container.
10+
/// </summary>
11+
/// <param name="name">The name of the resource.</param>
12+
/// <param name="password">The Oracle Database server password.</param>
13+
public class OracleDatabaseServerResource(string name, string password) : Resource(name), IOracleDatabaseParentResource
14+
{
15+
public string Password { get; } = password;
16+
17+
/// <summary>
18+
/// Gets the connection string for the Oracle Database server.
19+
/// </summary>
20+
/// <returns>A connection string for the Oracle Database server in the form "user id=system;password=password;data source=host:port".</returns>
21+
public string? GetConnectionString()
22+
{
23+
if (!this.TryGetAllocatedEndPoints(out var allocatedEndpoints))
24+
{
25+
throw new DistributedApplicationException("Expected allocated endpoints!");
26+
}
27+
28+
var allocatedEndpoint = allocatedEndpoints.Single(); // We should only have one endpoint for Oracle.
29+
30+
var connectionString = $"user id=system;password={PasswordUtil.EscapePassword(Password)};data source={allocatedEndpoint.Address}:{allocatedEndpoint.Port}";
31+
return connectionString;
32+
}
33+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<TargetFramework>$(NetCurrent)</TargetFramework>
5+
<IsPackable>true</IsPackable>
6+
<PackageTags>$(ComponentEfCorePackageTags) oracle sql</PackageTags>
7+
<Description>An Oracle Database provider for Entity Framework Core that integrates with Aspire, including connection pooling, health check, logging, and telemetry.</Description>
8+
</PropertyGroup>
9+
10+
<ItemGroup>
11+
<Compile Include="..\Common\HealthChecksExtensions.cs" Link="HealthChecksExtensions.cs" />
12+
<Compile Include="..\Common\ConfigurationSchemaAttributes.cs" Link="ConfigurationSchemaAttributes.cs" />
13+
</ItemGroup>
14+
15+
<ItemGroup>
16+
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" />
17+
<PackageReference Include="Microsoft.Extensions.Diagnostics.HealthChecks.EntityFrameworkCore" />
18+
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" />
19+
<PackageReference Include="OpenTelemetry.Extensions.Hosting" />
20+
<PackageReference Include="OpenTelemetry.Instrumentation.EntityFrameworkCore" />
21+
<PackageReference Include="OpenTelemetry.Instrumentation.EventCounters" />
22+
<PackageReference Include="Oracle.EntityFrameworkCore" />
23+
</ItemGroup>
24+
25+
</Project>

0 commit comments

Comments
 (0)