Skip to content

Commit 5926a06

Browse files
authored
bug: Fix EF wrapper MySQL option handling for plugins and user variables (#272)
1 parent f7008b5 commit 5926a06

16 files changed

Lines changed: 329 additions & 11 deletions

File tree

AwsWrapperDataProvider.EntityFrameworkCore.MySqlConnector/AwsWrapperDataProvider.EntityFrameworkCore.MySqlConnector.csproj

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
<Nullable>enable</Nullable>
77
</PropertyGroup>
88

9+
<ItemGroup>
10+
<InternalsVisibleTo Include="AwsWrapperDataProvider.EntityFrameworkCore.Tests" />
11+
</ItemGroup>
12+
913
<ItemGroup>
1014
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.14" />
1115
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="9.0.14" />

AwsWrapperDataProvider.EntityFrameworkCore.MySqlConnector/AwsWrapperDbContextOptionsBuilderExtensions.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License").
44
// You may not use this file except in compliance with the License.
@@ -12,7 +12,7 @@
1212
// See the License for the specific language governing permissions and
1313
// limitations under the License.
1414

15-
using AwsWrapperDataProvider.EntityFrameworkCore.MySQL;
15+
using AwsWrapperDataProvider.EntityFrameworkCore.MySqlConnector;
1616
using Microsoft.EntityFrameworkCore.Diagnostics;
1717
using Microsoft.EntityFrameworkCore.Infrastructure;
1818

AwsWrapperDataProvider.EntityFrameworkCore.MySqlConnector/AwsWrapperOptionsExtension.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
using Microsoft.Extensions.DependencyInjection;
1919
using Microsoft.Extensions.DependencyInjection.Extensions;
2020

21-
namespace AwsWrapperDataProvider.EntityFrameworkCore.MySQL;
21+
namespace AwsWrapperDataProvider.EntityFrameworkCore.MySqlConnector;
2222

2323
public class AwsWrapperOptionsExtension : IDbContextOptionsExtension
2424
{

AwsWrapperDataProvider.EntityFrameworkCore.MySqlConnector/AwsWrapperRelationalConnection.cs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
//
33
// Licensed under the Apache License, Version 2.0 (the "License").
44
// You may not use this file except in compliance with the License.
@@ -13,13 +13,15 @@
1313
// limitations under the License.
1414

1515
using System.Data.Common;
16+
using AwsWrapperDataProvider.EntityFrameworkCore.MySqlConnector.RelationalConnectionDialects;
1617
using Microsoft.EntityFrameworkCore.Storage;
1718

18-
namespace AwsWrapperDataProvider.EntityFrameworkCore.MySQL;
19+
namespace AwsWrapperDataProvider.EntityFrameworkCore.MySqlConnector;
1920

2021
public class AwsWrapperRelationalConnection : RelationalConnection, IAwsWrapperRelationalConnection
2122
{
2223
private readonly string wrapperConnectionString;
24+
private readonly IRelationalConnectionDialect relationalConnectionDialect;
2325

2426
public AwsWrapperRelationalConnection(
2527
RelationalConnectionDependencies dependencies, IRelationalConnection targetRelationalConnection) : base(dependencies)
@@ -30,13 +32,19 @@ public AwsWrapperRelationalConnection(
3032
.OfType<AwsWrapperOptionsExtension>()
3133
.FirstOrDefault();
3234

33-
this.wrapperConnectionString = extension?.WrapperConnectionString ?? throw new InvalidOperationException("AwsWrapperOptionsExtension not found.");
35+
if (extension is null)
36+
{
37+
throw new InvalidOperationException("AwsWrapperOptionsExtension not found.");
38+
}
39+
40+
this.relationalConnectionDialect = RelationalConnectionDialectProvider.GetDialect(extension.WrappedExtension);
41+
this.wrapperConnectionString = this.relationalConnectionDialect.NormalizeConnectionString(extension.WrapperConnectionString);
3442
}
3543

3644
public IRelationalConnection? TargetRelationalConnection { get; set; }
3745

3846
protected override DbConnection CreateDbConnection()
3947
{
40-
return new AwsWrapperConnection(typeof(MySqlConnector.MySqlConnection), this.wrapperConnectionString!);
48+
return new AwsWrapperConnection(this.relationalConnectionDialect.UnderlyingConnectionType, this.wrapperConnectionString);
4149
}
4250
}

AwsWrapperDataProvider.EntityFrameworkCore.MySqlConnector/IAwsWrapperRelationalConnection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515
using Microsoft.EntityFrameworkCore.Storage;
1616

17-
namespace AwsWrapperDataProvider.EntityFrameworkCore.MySQL;
17+
namespace AwsWrapperDataProvider.EntityFrameworkCore.MySqlConnector;
1818

1919
public interface IAwsWrapperRelationalConnection : IRelationalConnection
2020
{

AwsWrapperDataProvider.EntityFrameworkCore.MySqlConnector/README.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,21 @@ services.AddDbContext<MyDbContext>(options =>
2121
wrappedOptions => wrappedOptions.UseMySql(connectionString)));
2222
```
2323

24+
## Custom Provider Registration
25+
26+
The wrapper automatically detects supported EF Core MySQL providers (e.g. Pomelo) by matching the assembly name prefix of the wrapped options extension. If you are using an unsupported or custom EF Core MySQL provider, you can register it manually using `RelationalConnectionDialectProvider.RegisterDialect`:
27+
28+
```csharp
29+
using AwsWrapperDataProvider.EntityFrameworkCore.MySqlConnector.RelationalConnectionDialects;
30+
31+
// Register a custom dialect before configuring the DbContext.
32+
RelationalConnectionDialectProvider.RegisterDialect(
33+
"MyCustom.EntityFrameworkCore.MySql",
34+
MyCustomRelationalConnectionDialect.Instance);
35+
```
36+
37+
The first argument is the assembly name prefix that the provider's options extension belongs to. The second argument is an implementation of `IRelationalConnectionDialect` that specifies the underlying connection type and any connection string normalization.
38+
2439
## Example
2540

2641
See the [MySqlEntityFrameworkExample](../docs/examples/MySqlEntityFrameworkExample/) project for a complete working demonstration.
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License").
4+
// You may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
namespace AwsWrapperDataProvider.EntityFrameworkCore.MySqlConnector.RelationalConnectionDialects;
16+
17+
public static class EfMySqlAssemblyPrefixes
18+
{
19+
public static string Pomelo = "Pomelo.EntityFrameworkCore.MySql";
20+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License").
4+
// You may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
namespace AwsWrapperDataProvider.EntityFrameworkCore.MySqlConnector.RelationalConnectionDialects;
16+
17+
/// <summary>
18+
/// Per–EF-MySQL-provider rules for the wrapper relational connection (underlying ADO.NET type and wrapper connection-string normalization).
19+
/// </summary>
20+
public interface IRelationalConnectionDialect
21+
{
22+
/// <summary>
23+
/// Gets the target connection type passed to <see cref="AwsWrapperDataProvider.AwsWrapperConnection"/>.
24+
/// </summary>
25+
Type UnderlyingConnectionType { get; }
26+
27+
/// <summary>
28+
/// Normalizes the user-supplied wrapper connection string before opening the wrapper connection.
29+
/// </summary>
30+
/// <param name="wrapperConnectionString">Connection string including wrapper keys (e.g. <c>Plugins=</c>).</param>
31+
/// <returns>Connection string to store on the opened <see cref="AwsWrapperDataProvider.AwsWrapperConnection"/>.</returns>
32+
string NormalizeConnectionString(string wrapperConnectionString);
33+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License").
4+
// You may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System.Data.Common;
16+
using System.Reflection;
17+
using MySqlConnector;
18+
19+
namespace AwsWrapperDataProvider.EntityFrameworkCore.MySqlConnector.RelationalConnectionDialects;
20+
21+
/// <summary>
22+
/// Pomelo <c>EntityFrameworkCore.MySql</c>: targets <see cref="MySqlConnection"/>. Normalization invokes Pomelo's internal
23+
/// mandatory connection-string options when present; otherwise applies the same MySqlConnector defaults manually.
24+
/// </summary>
25+
internal sealed class PomeloEfMySqlRelationalConnectionDialect : IRelationalConnectionDialect
26+
{
27+
internal static readonly PomeloEfMySqlRelationalConnectionDialect Instance = new();
28+
29+
private PomeloEfMySqlRelationalConnectionDialect()
30+
{
31+
}
32+
33+
/// <inheritdoc />
34+
public Type UnderlyingConnectionType => typeof(MySqlConnection);
35+
36+
/// <inheritdoc />
37+
public string NormalizeConnectionString(string wrapperConnectionString)
38+
{
39+
var cs = wrapperConnectionString;
40+
ApplyManualMySqlConnectorMandatoryDefaults(ref cs);
41+
return cs;
42+
}
43+
44+
private static void ApplyManualMySqlConnectorMandatoryDefaults(ref string connectionString)
45+
{
46+
var builder = new DbConnectionStringBuilder
47+
{
48+
ConnectionString = connectionString,
49+
};
50+
51+
builder["AllowUserVariables"] = true;
52+
builder["UseAffectedRows"] = false;
53+
connectionString = builder.ConnectionString;
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License").
4+
// You may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using Microsoft.EntityFrameworkCore.Infrastructure;
16+
17+
namespace AwsWrapperDataProvider.EntityFrameworkCore.MySqlConnector.RelationalConnectionDialects;
18+
19+
/// <summary>
20+
/// Selects <see cref="IRelationalConnectionDialect"/> for the configured EF Core MySQL provider.
21+
/// </summary>
22+
public static class RelationalConnectionDialectProvider
23+
{
24+
private static readonly Dictionary<string, IRelationalConnectionDialect> DialectsByAssemblyPrefix = new()
25+
{
26+
{ EfMySqlAssemblyPrefixes.Pomelo, PomeloEfMySqlRelationalConnectionDialect.Instance },
27+
};
28+
29+
/// <summary>
30+
/// Returns the dialect for the EF MySQL provider that registered <paramref name="wrappedExtension"/>.
31+
/// </summary>
32+
/// <param name="wrappedExtension">The wrapped options extension (e.g. from <c>UseMySql</c>).</param>
33+
/// <returns>The dialect instance.</returns>
34+
/// <exception cref="InvalidOperationException">Thrown when no supported provider is detected.</exception>
35+
public static IRelationalConnectionDialect GetDialect(IDbContextOptionsExtension? wrappedExtension)
36+
{
37+
if (wrappedExtension is not null)
38+
{
39+
var assemblyName = wrappedExtension.GetType().Assembly.GetName().Name ?? string.Empty;
40+
foreach (var (prefix, dialect) in DialectsByAssemblyPrefix)
41+
{
42+
if (assemblyName.StartsWith(prefix, StringComparison.Ordinal))
43+
{
44+
return dialect;
45+
}
46+
}
47+
}
48+
49+
throw new InvalidOperationException(BuildUnsupportedRelationalConnectionMessage(wrappedExtension));
50+
}
51+
52+
public static void RegisterDialect(string assemblyPrefix, IRelationalConnectionDialect dialect)
53+
{
54+
DialectsByAssemblyPrefix[assemblyPrefix] = dialect;
55+
}
56+
57+
private static string BuildUnsupportedRelationalConnectionMessage(
58+
IDbContextOptionsExtension? wrappedExtension)
59+
{
60+
var extensionDetail = wrappedExtension is null
61+
? "none (wrapped extension was null)"
62+
: $"{wrappedExtension.GetType().FullName} (Assembly={wrappedExtension.GetType().Assembly.GetName().Name})";
63+
64+
return
65+
"The relational connection from the wrapped Entity Framework Core provider is not supported for AWS Advanced .NET Data Provider MySQL integration. " +
66+
$"Wrapped extension: {extensionDetail}. " +
67+
"Supported provider: Pomelo.EntityFrameworkCore.MySql.";
68+
}
69+
}

0 commit comments

Comments
 (0)