Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds Aspire Oracle EntityFrameworkCore Database component. #1295

Merged
merged 18 commits into from
Jan 3, 2024

Conversation

andrevlins
Copy link
Contributor

No description provided.

@dotnet-issue-labeler dotnet-issue-labeler bot added the area-integrations Issues pertaining to Aspire Integrations packages label Dec 8, 2023
@DamianEdwards
Copy link
Member

Thanks for this! Can you please log an issue that represents this work?

@andrevlins
Copy link
Contributor Author

Thanks for this! Can you please log an issue that represents this work?

Of course, I created the issue #1307

@DamianEdwards DamianEdwards linked an issue Dec 8, 2023 that may be closed by this pull request
@mitchdenny
Copy link
Member

Triggered package migration for the Oracle EF NuGet package to unblock the build.

@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the .Database suffix? The NuGet package from Oracle does not have this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to follow the existing pattern of other EF components where we have Aspire.X.EntityFrameworkCore.Y where:

X is the company/project
Y is the product

Oracle calls its product Oracle Database, so I followed this pattern to leave it open for other Oracle products that may support EntityFramework.

Aspire.Oracle.EntityFrameworkCore.Database
Aspire.Microsoft.EntityFrameworkCore.Cosmos
Aspire.Microsoft.EntityFrameworkCore.SqlServer
Aspire.Npgsql.EntityFrameworkCore.PostgreSQL

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. I'm happy with that as a justification as long as @eerhardt is happy as the czar of Aspire components ;)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The naming pattern we use is in

## Naming
- Each component's name must have the prefix `Aspire.`.
- When component is built around `ABC` client library, it should contain the client library name in its name. Example: `Aspire.ABC`. Where the technology has a particular casing we have preferred that: for example `Aspire.RabbitMQ` rather than `Aspire.RabbitMq`.

This should be called Aspire.Oracle.EntityFrameworkCore.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@mitchdenny
Copy link
Member

@eerhardt PTAL for the component side of this?

@mitchdenny
Copy link
Member

@andrevlins we need APIs for the AppModel side of things are you planning on adding this? There isn't much point having a component API surface without the corresponding AppModel APIs :)

Take a look at the following for inspiration:
https://github.com/dotnet/aspire/tree/main/src/Aspire.Hosting/SqlServer

@mitchdenny mitchdenny added the area-app-model Issues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication label Dec 11, 2023
@joperezr joperezr added the community-contribution Indicates that the PR has been added by a community member label Dec 11, 2023
@andrevlins
Copy link
Contributor Author

@andrevlins we need APIs for the AppModel side of things are you planning on adding this? There isn't much point having a component API surface without the corresponding AppModel APIs :)

Take a look at the following for inspiration: https://github.com/dotnet/aspire/tree/main/src/Aspire.Hosting/SqlServer

Yes I was doing this in another PR #1004
I brought the implementation from there and I will review it as there were some changes in the latest Aspire updates.

/// </summary>
/// <param name="name">The name of the resource.</param>
/// <param name="connectionString">The Oracle Database connection string.</param>
public class OracleDatabaseConnectionResource(string name, string? connectionString) : Resource(name), IOracleDatabaseResource
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can remove this type and its associated extension method. We've opted not to have XYZConnection resource types for now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@mitchdenny
Copy link
Member

I brought the implementation from there and I will review it as there were some changes in the latest Aspire updates.

Yep, we made some changes to try and clarify a few things.

You don't need the Oracle connection type anymore - with have a WithReference(ConnectionString) extension method for that kind of thing.

You'll need to add OracleServerResource in addition to OracleContainerResource. OracleServerResource would emit oracle.server.v0 to the manifest and OracleContainerResource would emit as a container.v0 resource.

The IOracleResource would become IOracleParentResource and both the container and server resource types would implement it ... so that the AddDatabase(...) extension would work on both of them. Take a look at the Postgres implementation for concrete examples of how this was setup.

Apologies for the shifting API underneath you we are still shifting things around a bit. If we can get this change in though, it means that when we do future API changes we have to keep this up to date ;)

@andrevlins
Copy link
Contributor Author

You don't need the Oracle connection type anymore - with have a WithReference(ConnectionString) extension method for that kind of thing.

You'll need to add OracleServerResource in addition to OracleContainerResource. OracleServerResource would emit oracle.server.v0 to the manifest and OracleContainerResource would emit as a container.v0 resource.

The IOracleResource would become IOracleParentResource and both the container and server resource types would implement it ... so that the AddDatabase(...) extension would work on both of them. Take a look at the Postgres implementation for concrete examples of how this was setup.

Thank you very much for the explanations, I believe I made all the adaptations mentioned.

Apologies for the shifting API underneath you we are still shifting things around a bit. If we can get this change in though, it means that when we do future API changes we have to keep this up to date ;)

No problem! It's been great to follow the evolution of this project.

@@ -24,7 +24,7 @@ public static class OracleDatabaseBuilderExtensions
/// <returns>A reference to the <see cref="IResourceBuilder{OracleDatabaseContainerResource}"/>.</returns>
public static IResourceBuilder<OracleDatabaseContainerResource> AddOracleDatabaseContainer(this IDistributedApplicationBuilder builder, string name, int? port = null, string? password = null)
{
password = password ?? Guid.NewGuid().ToString("N");
password = password ?? Guid.NewGuid().ToString("N").Substring(0, 30);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should centralize this generated password logic into PasswordUtils @mitchdenny

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that it is different than SqlServer.

password = password ?? Guid.NewGuid().ToString("N") + Guid.NewGuid().ToString("N").ToUpper();

and MySql

password ??= Guid.NewGuid().ToString("N");

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably just come up with a decent password generator that we can use. What I did for SQL Server was half baked and really only appropriate for inner loop (which luckily is where it is used). I'd be happy to see this go in without change this password generation logic and instead see an issue created where we properly develop out PasswordUtils than can meet our various use cases (needs to be flexible enough to side step various limitations around what the containerized tool can used, and what we can escape in the connection string.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know an issue hasn't been created yet, but I saw this comment and opened a PR that might address it: #1469.

[Theory]
[InlineData(true)]
[InlineData(false)]
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "EF1001:Internal EF Core API usage.", Justification = "Required to verify pooling without touching DB")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you move this to a #pragma only around the code that needs it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link
Member

@eerhardt eerhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is getting pretty close. Thanks for this work @andrevlins.

@@ -248,3 +248,30 @@ Aspire.StackExchange.Redis.OutputCaching:
- Everything from `Aspire.StackExchange.Redis` plus:
- Log categories:
- "Microsoft.AspNetCore.OutputCaching.StackExchangeRedis"

Aspire.Oracle.EntityFrameworkCore:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(nit) Can you keep these are alphabetically ordered?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Of course, I didn't understand the ordering, sorry.

builder.AddOracleDatabaseDbContext<MyDbContext>("orcl", settings => settings.HealthChecks = false);
```

## AppHost extensions
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should also have a code snippet for the code to call AddOracleDatabaseDbContext in the service project.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment on lines 21 to 27
/// <summary>
/// <para>Gets or sets the maximum number of retry attempts.</para>
/// <value>
/// The default is 6.
/// Set it to 0 to disable the retry mechanism.
/// </value>
/// </summary>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// <summary>
/// <para>Gets or sets the maximum number of retry attempts.</para>
/// <value>
/// The default is 6.
/// Set it to 0 to disable the retry mechanism.
/// </value>
/// </summary>
/// <summary>
/// <para>Gets or sets the maximum number of retry attempts.</para>
/// <para>Default value is 6, set it to 0 to disable the retry mechanism.</para>
/// </summary>

value isn't a valid element in summary. Repeated elsewhere. See

/// <summary>
/// <para>Gets or sets the maximum number of retry attempts.</para>
/// <para>Default value is 6, set it to 0 to disable the retry mechanism.</para>
/// </summary>
public int MaxRetryCount { get; set; } = 6;
/// <summary>
/// Gets or sets a boolean value that indicates whether the database health check is enabled or not.
/// </summary>
/// <value>
/// The default value is <see langword="true"/>.
/// </value>
public bool HealthChecks { get; set; } = true;

for the latest format.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All right

"Aspire": {
"Oracle": {
"EntityFrameworkCore": {
"Database": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is out-dated.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -8,13 +8,15 @@
builder.AddNpgsqlDataSource("postgresdb");
builder.AddRabbitMQ("rabbitmqcontainer");
builder.AddMongoDBClient("mymongodb");
builder.AddOracleClient("freepdb1");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test program should be using the Oracle EF Component you are adding, not a workaround method and talking to an OracleConnection directly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. It makes perfect sense, I got a little lost here. Thank you for the explanation.

app.MapGet("/oracledatabase/verify", VerifyOracleDatabaseAsync);
}

private static async Task<IResult> VerifyOracleDatabaseAsync(OracleConnection connection)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be

Suggested change
private static async Task<IResult> VerifyOracleDatabaseAsync(OracleConnection connection)
private static async Task<IResult> VerifyOracleDatabaseAsync(MyDbContext dbContext)

And then using the EF Component you are adding in this PR.

@mitchdenny
Copy link
Member

I think we should merge this once preview 2 is out the door.

Copy link
Member

@eerhardt eerhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a few more comments to fix up, and then I think this will be good to merge.

Thanks again for the contribution!

Aspire.sln Outdated Show resolved Hide resolved
src/Aspire.Hosting/Oracle/IOracleDatabaseResource.cs Outdated Show resolved Hide resolved
src/Components/Aspire.Oracle.EntityFrameworkCore/README.md Outdated Show resolved Hide resolved
src/Components/Aspire.Oracle.EntityFrameworkCore/README.md Outdated Show resolved Hide resolved
@andrevlins
Copy link
Contributor Author

@eerhardt I will update the pr with the new updates to solve the build problems

@andrevlins
Copy link
Contributor Author

@eerhardt I will update the pr with the new updates to solve the build problems

Ready

Copy link
Member

@mitchdenny mitchdenny left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

Copy link
Member

@eerhardt eerhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks great. Thanks @andrevlins for the contribution!

@eerhardt eerhardt merged commit 398dd70 into dotnet:main Jan 3, 2024
8 checks passed
@andrevlins andrevlins deleted the aspire-oracle-ef branch January 5, 2024 00:19
@github-actions github-actions bot locked and limited conversation to collaborators Apr 27, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-app-model Issues pertaining to the APIs in Aspire.Hosting, e.g. DistributedApplication area-integrations Issues pertaining to Aspire Integrations packages community-contribution Indicates that the PR has been added by a community member
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add Aspire Oracle EntityFrameworkCore Database component
6 participants