diff --git a/FruityFoundation.DataAccess.Abstractions/FruityFoundation.DataAccess.Abstractions.csproj b/FruityFoundation.DataAccess.Abstractions/FruityFoundation.DataAccess.Abstractions.csproj
index c5fa411..e87adc6 100644
--- a/FruityFoundation.DataAccess.Abstractions/FruityFoundation.DataAccess.Abstractions.csproj
+++ b/FruityFoundation.DataAccess.Abstractions/FruityFoundation.DataAccess.Abstractions.csproj
@@ -4,6 +4,14 @@
enable
enable
net8.0;net6.0
+ true
+ Kyle Ratti
+ https://github.com/kyleratti/FruityFoundation
+ true
+
+
+
+
diff --git a/FruityFoundation.DataAccess.Abstractions/INonTransactionalDbConnection.cs b/FruityFoundation.DataAccess.Abstractions/INonTransactionalDbConnection.cs
index 9dcef19..2a8789c 100644
--- a/FruityFoundation.DataAccess.Abstractions/INonTransactionalDbConnection.cs
+++ b/FruityFoundation.DataAccess.Abstractions/INonTransactionalDbConnection.cs
@@ -5,7 +5,6 @@ namespace FruityFoundation.DataAccess.Abstractions;
public interface INonTransactionalDbConnection : IDatabaseConnection, IDisposable, IAsyncDisposable
where TConnectionType : ConnectionType
{
- public Task> CreateTransaction();
- public Task> CreateTransaction(IsolationLevel isolationLevel);
- public Task> CreateTransaction(IsolationLevel isolationLevel, bool deferred);
+ public Task> CreateTransaction(CancellationToken cancellationToken);
+ public Task> CreateTransaction(IsolationLevel isolationLevel, CancellationToken cancellationToken);
}
diff --git a/FruityFoundation.DataAccess.Core/DbConnectionFactory.cs b/FruityFoundation.DataAccess.Core/DbConnectionFactory.cs
new file mode 100644
index 0000000..84dd0ca
--- /dev/null
+++ b/FruityFoundation.DataAccess.Core/DbConnectionFactory.cs
@@ -0,0 +1,30 @@
+using System.Data.Common;
+using FruityFoundation.DataAccess.Abstractions;
+
+namespace FruityFoundation.DataAccess.Core;
+
+public class DbConnectionFactory : IDbConnectionFactory
+{
+ private readonly Func _readWriteConnectionFactory;
+ private readonly Func _readOnlyConnectionFactory;
+
+ public DbConnectionFactory(Func readWriteConnectionFactory, Func readOnlyConnectionFactory)
+ {
+ _readWriteConnectionFactory = readWriteConnectionFactory;
+ _readOnlyConnectionFactory = readOnlyConnectionFactory;
+ }
+
+ public INonTransactionalDbConnection CreateConnection()
+ {
+ var connection = _readWriteConnectionFactory();
+ var nonTxConnection = new NonTransactionalDbConnection(connection);
+ return nonTxConnection;
+ }
+
+ public INonTransactionalDbConnection CreateReadOnlyConnection()
+ {
+ var connection = _readOnlyConnectionFactory();
+ var nonTxConnection = new NonTransactionalDbConnection(connection);
+ return nonTxConnection;
+ }
+}
diff --git a/FruityFoundation.DataAccess.Sqlite/DbTransaction.cs b/FruityFoundation.DataAccess.Core/DbTransaction.cs
similarity index 86%
rename from FruityFoundation.DataAccess.Sqlite/DbTransaction.cs
rename to FruityFoundation.DataAccess.Core/DbTransaction.cs
index 609e4af..1420da7 100644
--- a/FruityFoundation.DataAccess.Sqlite/DbTransaction.cs
+++ b/FruityFoundation.DataAccess.Core/DbTransaction.cs
@@ -2,21 +2,20 @@
using System.Runtime.CompilerServices;
using Dapper;
using FruityFoundation.DataAccess.Abstractions;
-using Microsoft.Data.Sqlite;
-namespace FruityFoundation.DataAccess.Sqlite;
+namespace FruityFoundation.DataAccess.Core;
+// ReSharper disable once ClassWithVirtualMembersNeverInherited.Global
public class DbTransaction : IDatabaseTransactionConnection
where TConnectionType : ConnectionType
{
- private readonly SqliteTransaction _transaction;
+ private readonly DbTransaction _transaction;
- internal DbTransaction(SqliteTransaction transaction)
+ internal DbTransaction(DbTransaction transaction)
{
_transaction = transaction;
}
- ///
///
public async Task> Query(
string sql,
@@ -109,17 +108,34 @@ public class DbTransaction : IDatabaseTransactionConnection
public void Dispose()
{
- _transaction.Dispose();
+ Dispose(true);
GC.SuppressFinalize(this);
}
+ protected virtual async ValueTask DisposeAsyncCore()
+ {
+#pragma warning disable IDISP007
+ await _transaction.DisposeAsync();
+#pragma warning restore IDISP007
+ }
+
///
public async ValueTask DisposeAsync()
{
- await _transaction.DisposeAsync();
+ await DisposeAsyncCore();
GC.SuppressFinalize(this);
}
}
diff --git a/FruityFoundation.DataAccess.Sqlite/FruityFoundation.DataAccess.Sqlite.csproj b/FruityFoundation.DataAccess.Core/FruityFoundation.DataAccess.Core.csproj
similarity index 57%
rename from FruityFoundation.DataAccess.Sqlite/FruityFoundation.DataAccess.Sqlite.csproj
rename to FruityFoundation.DataAccess.Core/FruityFoundation.DataAccess.Core.csproj
index 3fdb130..6d43d4b 100644
--- a/FruityFoundation.DataAccess.Sqlite/FruityFoundation.DataAccess.Sqlite.csproj
+++ b/FruityFoundation.DataAccess.Core/FruityFoundation.DataAccess.Core.csproj
@@ -3,17 +3,23 @@
enable
enable
+ true
+ Kyle Ratti
+ https://github.com/kyleratti/FruityFoundation
+ true
net8.0;net6.0
+
+
+
+
-
-
diff --git a/FruityFoundation.DataAccess.Core/NonTransactionalDbConnection.cs b/FruityFoundation.DataAccess.Core/NonTransactionalDbConnection.cs
new file mode 100644
index 0000000..2c898e9
--- /dev/null
+++ b/FruityFoundation.DataAccess.Core/NonTransactionalDbConnection.cs
@@ -0,0 +1,124 @@
+using System.Data;
+using System.Data.Common;
+using System.Runtime.CompilerServices;
+using Dapper;
+using FruityFoundation.DataAccess.Abstractions;
+
+namespace FruityFoundation.DataAccess.Core;
+
+// ReSharper disable once ClassWithVirtualMembersNeverInherited.Global
+public class NonTransactionalDbConnection : INonTransactionalDbConnection
+ where TConnectionType : ConnectionType
+{
+ private readonly DbConnection _connection;
+
+ ///
+ /// C'tor
+ ///
+ public NonTransactionalDbConnection(DbConnection connection)
+ {
+ _connection = connection;
+ }
+
+ ///
+ public async Task> Query(
+ string sql,
+ object? param = null,
+ CancellationToken cancellationToken = default
+ ) => await _connection.QueryAsync(new CommandDefinition(sql, param, cancellationToken: cancellationToken));
+
+ ///
+ public async IAsyncEnumerable QueryUnbuffered(
+ string sql,
+ object? param = null,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default
+ )
+ {
+ var query = _connection.QueryUnbufferedAsync(sql, param, transaction: null)
+ .WithCancellation(cancellationToken);
+
+ await foreach (var item in query)
+ yield return item;
+ }
+
+ ///
+ public async Task QuerySingle(
+ string sql,
+ object? param = null,
+ CancellationToken cancellationToken = default
+ ) => await _connection.QuerySingleAsync(new CommandDefinition(sql, param, cancellationToken: cancellationToken));
+
+ ///
+ public async Task Execute(
+ string sql,
+ object? param = null,
+ CancellationToken cancellationToken = default
+ ) => await _connection.ExecuteAsync(new CommandDefinition(sql, param, cancellationToken: cancellationToken));
+
+ ///
+ public async Task ExecuteScalar(
+ string sql,
+ object? param = null,
+ CancellationToken cancellationToken = default
+ ) => await _connection.ExecuteScalarAsync(new CommandDefinition(sql, param, cancellationToken: cancellationToken));
+
+ ///
+ public async Task ExecuteReader(
+ string sql,
+ object? param = null,
+ CancellationToken cancellationToken = default
+ ) => await _connection.ExecuteReaderAsync(new CommandDefinition(sql, param, cancellationToken: cancellationToken));
+
+ ///
+ public async Task> CreateTransaction(CancellationToken cancellationToken)
+ {
+ if (!_connection.State.HasFlag(ConnectionState.Open))
+ await _connection.OpenAsync(cancellationToken);
+
+ var tx = await _connection.BeginTransactionAsync(cancellationToken);
+
+ return new DbTransaction(tx);
+ }
+
+ ///
+ public async Task> CreateTransaction(IsolationLevel isolationLevel, CancellationToken cancellationToken)
+ {
+ if (!_connection.State.HasFlag(ConnectionState.Open))
+ await _connection.OpenAsync(cancellationToken);
+
+ var tx = await _connection.BeginTransactionAsync(isolationLevel, cancellationToken);
+
+ return new DbTransaction(tx);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+#pragma warning disable IDISP007
+ _connection.Dispose();
+#pragma warning restore IDISP007
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual async ValueTask DisposeAsyncCore()
+ {
+#pragma warning disable IDISP007
+ await _connection.DisposeAsync();
+#pragma warning restore IDISP007
+ }
+
+ ///
+ public async ValueTask DisposeAsync()
+ {
+ await DisposeAsyncCore();
+ GC.SuppressFinalize(this);
+ }
+}
diff --git a/FruityFoundation.DataAccess.Sqlite/DbConnectionFactory.cs b/FruityFoundation.DataAccess.Sqlite/DbConnectionFactory.cs
deleted file mode 100644
index a946050..0000000
--- a/FruityFoundation.DataAccess.Sqlite/DbConnectionFactory.cs
+++ /dev/null
@@ -1,33 +0,0 @@
-using FruityFoundation.DataAccess.Abstractions;
-
-namespace FruityFoundation.DataAccess.Sqlite;
-
-public class DbConnectionFactory : IDbConnectionFactory
-{
- private readonly string _readWriteConnectionString;
- private readonly string _readOnlyConnectionString;
-
- public DbConnectionFactory(string readWriteConnectionString, string readOnlyConnectionString)
- {
- _readWriteConnectionString = readWriteConnectionString;
- _readOnlyConnectionString = readOnlyConnectionString;
- }
-
- public INonTransactionalDbConnection CreateConnection()
- {
- if (string.IsNullOrWhiteSpace(_readWriteConnectionString))
- throw new ApplicationException("ReadWrite connection string cannot be null or empty.");
-
- var connection = new NonTransactionalDbConnection(_readWriteConnectionString);
- return connection;
- }
-
- public INonTransactionalDbConnection CreateReadOnlyConnection()
- {
- if (string.IsNullOrWhiteSpace(_readOnlyConnectionString))
- throw new ApplicationException("ReadOnly connection string cannot be null or empty.");
-
- var connection = new NonTransactionalDbConnection(_readOnlyConnectionString);
- return connection;
- }
-}
diff --git a/FruityFoundation.DataAccess.Sqlite/NonTransactionalDbConnection.cs b/FruityFoundation.DataAccess.Sqlite/NonTransactionalDbConnection.cs
deleted file mode 100644
index 2caf299..0000000
--- a/FruityFoundation.DataAccess.Sqlite/NonTransactionalDbConnection.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-using System.Data;
-using System.Data.Common;
-using System.Runtime.CompilerServices;
-using Dapper;
-using FruityFoundation.DataAccess.Abstractions;
-using Microsoft.Data.Sqlite;
-
-namespace FruityFoundation.DataAccess.Sqlite;
-
-// ReSharper disable once UnusedTypeParameter
-public class NonTransactionalDbConnection : SqliteConnection, INonTransactionalDbConnection
- where TConnectionType : ConnectionType
-{
- ///
- /// C'tor
- ///
- public NonTransactionalDbConnection(string connectionString) : base(connectionString)
- {
- }
-
- ///
- public async Task> Query(
- string sql,
- object? param = null,
- CancellationToken cancellationToken = default
- ) => await this.QueryAsync(new CommandDefinition(sql, param, cancellationToken: cancellationToken));
-
- ///
- public async IAsyncEnumerable QueryUnbuffered(
- string sql,
- object? param = null,
- [EnumeratorCancellation] CancellationToken cancellationToken = default
- )
- {
- var query = this.QueryUnbufferedAsync(sql, param, transaction: null)
- .WithCancellation(cancellationToken);
-
- await foreach (var item in query)
- yield return item;
- }
-
- ///
- public async Task QuerySingle(
- string sql,
- object? param = null,
- CancellationToken cancellationToken = default
- ) => await this.QuerySingleAsync(new CommandDefinition(sql, param, cancellationToken: cancellationToken));
-
- ///
- public async Task Execute(
- string sql,
- object? param = null,
- CancellationToken cancellationToken = default
- ) => await this.ExecuteAsync(new CommandDefinition(sql, param, cancellationToken: cancellationToken));
-
- ///
- public async Task ExecuteScalar(
- string sql,
- object? param = null,
- CancellationToken cancellationToken = default
- ) => await this.ExecuteScalarAsync(new CommandDefinition(sql, param, cancellationToken: cancellationToken));
-
- ///
- public async Task ExecuteReader(
- string sql,
- object? param = null,
- CancellationToken cancellationToken = default
- ) => await this.ExecuteReaderAsync(new CommandDefinition(sql, param, cancellationToken: cancellationToken));
-
- ///
- public async Task> CreateTransaction()
- {
- if (!State.HasFlag(ConnectionState.Open))
- await OpenAsync();
-
- var tx = BeginTransaction();
-
- return new DbTransaction(tx);
- }
-
- ///
- public async Task> CreateTransaction(IsolationLevel isolationLevel)
- {
- if (!State.HasFlag(ConnectionState.Open))
- await OpenAsync();
-
- var tx = BeginTransaction(isolationLevel);
-
- return new DbTransaction(tx);
- }
-
- ///
- public async Task> CreateTransaction(IsolationLevel isolationLevel, bool deferred)
- {
- if (!State.HasFlag(ConnectionState.Open))
- await OpenAsync();
-
- var tx = BeginTransaction(isolationLevel, deferred);
-
- return new DbTransaction(tx);
- }
-}
diff --git a/FruityFoundation.DataAccess.Sqlite/ServiceCollectionExtensions.cs b/FruityFoundation.DataAccess.Sqlite/ServiceCollectionExtensions.cs
deleted file mode 100644
index adc5e38..0000000
--- a/FruityFoundation.DataAccess.Sqlite/ServiceCollectionExtensions.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using FruityFoundation.DataAccess.Abstractions;
-using Microsoft.Extensions.DependencyInjection;
-
-namespace FruityFoundation.DataAccess.Sqlite;
-
-public static class ServiceCollectionExtensions
-{
- public static IServiceCollection AddSqliteConnectionFactory(this IServiceCollection services)
- {
- services.AddSingleton();
- return services;
- }
-}
diff --git a/FruityFoundation.DataAccess.Sqlite/SqliteConnectionExtensions.cs b/FruityFoundation.DataAccess.Sqlite/SqliteConnectionExtensions.cs
deleted file mode 100644
index c6bb5b3..0000000
--- a/FruityFoundation.DataAccess.Sqlite/SqliteConnectionExtensions.cs
+++ /dev/null
@@ -1,13 +0,0 @@
-using System.Data;
-using Microsoft.Data.Sqlite;
-
-namespace FruityFoundation.DataAccess.Sqlite;
-
-public static class SqliteConnectionExtensions
-{
- public static async Task CreateTransaction(this SqliteConnection connection, IsolationLevel isolationLevel)
- {
- await connection.OpenAsync();
- return connection.BeginTransaction(isolationLevel);
- }
-}
diff --git a/FruityFoundation.Tests.DataAccess.Sqlite/DbConnectionFactoryTests.cs b/FruityFoundation.Tests.DataAccess.Sqlite/DbConnectionFactoryTests.cs
deleted file mode 100644
index 56a709d..0000000
--- a/FruityFoundation.Tests.DataAccess.Sqlite/DbConnectionFactoryTests.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using FruityFoundation.DataAccess.Abstractions;
-using FruityFoundation.DataAccess.Sqlite;
-
-namespace FruityFoundation.Tests.DataAccess.Sqlite;
-
-public class DbConnectionFactoryTests
-{
- [TestCase("")]
- [TestCase(" ")]
- [TestCase(null)]
- public void CreateConnection_ThrowsException_WhenConnectionStringIsNullOrEmpty(string? connectionString)
- {
- // Arrange
- var dbConnectionFactory = new DbConnectionFactory(connectionString!, readOnlyConnectionString: "ReadOnlyConnectionString");
-
- // Act
- var exception = Assert.Throws(() => dbConnectionFactory.CreateConnection());
-
- // Assert
- Assert.That(exception, Is.Not.Null);
- Assert.That(exception.Message, Is.EqualTo("ReadWrite connection string cannot be null or empty."));
- }
-
- [TestCase("")]
- [TestCase(" ")]
- [TestCase(null)]
- public void CreateReadOnlyConnection_ThrowsException_WhenConnectionStringIsNullOrEmpty(string? connectionString)
- {
- // Arrange
- var dbConnectionFactory = new DbConnectionFactory(readWriteConnectionString: "connectionString", readOnlyConnectionString: null!);
-
- // Act
- var exception = Assert.Throws(() => dbConnectionFactory.CreateReadOnlyConnection());
-
- // Assert
- Assert.That(exception, Is.Not.Null);
- Assert.That(exception.Message, Is.EqualTo("ReadOnly connection string cannot be null or empty."));
- }
-
- [Test]
- public void CreateConnection_ReturnsNonTransactionalDbConnection_WhenConnectionStringIsValid()
- {
- // Arrange
- var dbConnectionFactory = new DbConnectionFactory(readWriteConnectionString: "Data Source=:memory:", readOnlyConnectionString: null!);
-
- // Act
- var connection = dbConnectionFactory.CreateConnection();
-
- // Assert
- Assert.That(connection, Is.Not.Null);
- Assert.That(connection, Is.InstanceOf>());
- }
-
- [Test]
- public void CreateReadOnlyConnection_ReturnsNonTransactionalDbConnection_WhenConnectionStringIsValid()
- {
- // Arrange
- var dbConnectionFactory = new DbConnectionFactory(readWriteConnectionString: null!, readOnlyConnectionString: "Data Source=:memory:;Mode=ReadOnly");
-
- // Act
- var connection = dbConnectionFactory.CreateReadOnlyConnection();
-
- // Assert
- Assert.That(connection, Is.Not.Null);
- Assert.That(connection, Is.InstanceOf>());
- }
-}
diff --git a/FruityFoundation.Tests.DataAccess.Sqlite/FruityFoundation.Tests.DataAccess.Sqlite.csproj b/FruityFoundation.Tests.DataAccess.Sqlite/FruityFoundation.Tests.DataAccess.Sqlite.csproj
deleted file mode 100644
index 77c87ec..0000000
--- a/FruityFoundation.Tests.DataAccess.Sqlite/FruityFoundation.Tests.DataAccess.Sqlite.csproj
+++ /dev/null
@@ -1,28 +0,0 @@
-
-
-
- enable
- enable
-
- false
- true
- net8.0;net6.0
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/FruityFoundation.sln b/FruityFoundation.sln
index f32a9e6..7b46a1c 100644
--- a/FruityFoundation.sln
+++ b/FruityFoundation.sln
@@ -18,9 +18,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "DataAccess", "DataAccess",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruityFoundation.DataAccess.Abstractions", "FruityFoundation.DataAccess.Abstractions\FruityFoundation.DataAccess.Abstractions.csproj", "{C003E247-C62E-4830-94E4-F274D8466A5C}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruityFoundation.DataAccess.Sqlite", "FruityFoundation.DataAccess.Sqlite\FruityFoundation.DataAccess.Sqlite.csproj", "{BB25E92F-5D51-487A-8937-27E28EF5E20F}"
-EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruityFoundation.Tests.DataAccess.Sqlite", "FruityFoundation.Tests.DataAccess.Sqlite\FruityFoundation.Tests.DataAccess.Sqlite.csproj", "{A2E62C7C-62A1-43C0-BD60-752B0C84E518}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FruityFoundation.DataAccess.Core", "FruityFoundation.DataAccess.Core\FruityFoundation.DataAccess.Core.csproj", "{B65527CC-218A-4EA3-93DC-985713B5DFF4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -56,21 +54,16 @@ Global
{C003E247-C62E-4830-94E4-F274D8466A5C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C003E247-C62E-4830-94E4-F274D8466A5C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C003E247-C62E-4830-94E4-F274D8466A5C}.Release|Any CPU.Build.0 = Release|Any CPU
- {BB25E92F-5D51-487A-8937-27E28EF5E20F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {BB25E92F-5D51-487A-8937-27E28EF5E20F}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {BB25E92F-5D51-487A-8937-27E28EF5E20F}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {BB25E92F-5D51-487A-8937-27E28EF5E20F}.Release|Any CPU.Build.0 = Release|Any CPU
- {A2E62C7C-62A1-43C0-BD60-752B0C84E518}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
- {A2E62C7C-62A1-43C0-BD60-752B0C84E518}.Debug|Any CPU.Build.0 = Debug|Any CPU
- {A2E62C7C-62A1-43C0-BD60-752B0C84E518}.Release|Any CPU.ActiveCfg = Release|Any CPU
- {A2E62C7C-62A1-43C0-BD60-752B0C84E518}.Release|Any CPU.Build.0 = Release|Any CPU
+ {B65527CC-218A-4EA3-93DC-985713B5DFF4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {B65527CC-218A-4EA3-93DC-985713B5DFF4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {B65527CC-218A-4EA3-93DC-985713B5DFF4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {B65527CC-218A-4EA3-93DC-985713B5DFF4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{50A75644-A1C3-4495-9DEB-DBB12D9334B5} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
{EBDC3640-4E47-43FE-BF0D-4BFFD07FE2EF} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
{A64E73D3-EF87-4938-B01E-F9CC0B59F9DE} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
{C003E247-C62E-4830-94E4-F274D8466A5C} = {5C3A014A-7931-4A36-95F0-5EFE15AB06A3}
- {BB25E92F-5D51-487A-8937-27E28EF5E20F} = {5C3A014A-7931-4A36-95F0-5EFE15AB06A3}
- {A2E62C7C-62A1-43C0-BD60-752B0C84E518} = {B44178DF-5B81-4029-90FA-2BF8E2A1EDBF}
+ {B65527CC-218A-4EA3-93DC-985713B5DFF4} = {5C3A014A-7931-4A36-95F0-5EFE15AB06A3}
EndGlobalSection
EndGlobal