From 7860d73b61bfe8b225dc3ec4cae4ffc6ef05c719 Mon Sep 17 00:00:00 2001 From: AndriySvyryd Date: Tue, 9 Aug 2022 14:47:29 -0700 Subject: [PATCH] Support AAD RBAC via TokenCredential Add a method to get the configured database name Fixes #26491 Fixes #25063 --- .github/workflows/TestCosmos.yaml | 7 ++ .../CosmosDatabaseFacadeExtensions.cs | 22 +++++- .../CosmosDbContextOptionsExtensions.cs | 69 +++++++++++++++++++ .../Internal/CosmosDbOptionExtension.cs | 49 ++++++++++++- .../Internal/CosmosSingletonOptions.cs | 11 +++ .../Internal/ICosmosSingletonOptions.cs | 9 +++ .../Internal/CosmosConventionSetBuilder.cs | 2 +- .../Properties/CosmosStrings.Designer.cs | 2 +- .../Properties/CosmosStrings.resx | 2 +- .../Internal/SingletonCosmosClientWrapper.cs | 7 +- .../RelationalConventionSetBuilder.cs | 4 +- .../SqlServerConventionSetBuilder.cs | 4 +- .../ProviderConventionSetBuilder.cs | 2 +- .../NavigationAttributeConventionBase.cs | 16 ++--- .../ConfigPatternsCosmosTest.cs | 1 + .../ConnectionSpecificationTest.cs | 38 ++++++++++ .../EFCore.Cosmos.FunctionalTests.csproj | 1 + .../TestUtilities/CosmosTestStore.cs | 3 + .../TestUtilities/TestEnvironment.cs | 4 ++ .../Internal/ModelCodeGeneratorTestBase.cs | 1 - 20 files changed, 232 insertions(+), 22 deletions(-) diff --git a/.github/workflows/TestCosmos.yaml b/.github/workflows/TestCosmos.yaml index 7c975c16e4b..05d2c2ad287 100644 --- a/.github/workflows/TestCosmos.yaml +++ b/.github/workflows/TestCosmos.yaml @@ -33,3 +33,10 @@ jobs: - name: Test on Cosmos run: test.cmd /p:Projects=${{ github.workspace }}\test\EFCore.Cosmos.FunctionalTests\EFCore.Cosmos.FunctionalTests.csproj shell: cmd + + - name: Publish Test Results + uses: actions/upload-artifact@v3 + if: always() + with: + name: test-results + path: artifacts/TestResults/Debug/* diff --git a/src/EFCore.Cosmos/Extensions/CosmosDatabaseFacadeExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosDatabaseFacadeExtensions.cs index ca1e6dcfcb0..f5c5d179b0b 100644 --- a/src/EFCore.Cosmos/Extensions/CosmosDatabaseFacadeExtensions.cs +++ b/src/EFCore.Cosmos/Extensions/CosmosDatabaseFacadeExtensions.cs @@ -26,8 +26,9 @@ public static CosmosClient GetCosmosClient(this DatabaseFacade databaseFacade) => GetService(databaseFacade).Client; private static TService GetService(IInfrastructure databaseFacade) + where TService : class { - var service = databaseFacade.Instance.GetService(); + var service = databaseFacade.GetService(); if (service == null) { throw new InvalidOperationException(CosmosStrings.CosmosNotInUse); @@ -36,6 +37,25 @@ private static TService GetService(IInfrastructure d return service; } + /// + /// Gets the configured database name for this . + /// + /// + /// See Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// The for the context. + /// The database name. + public static string GetCosmosDatabaseId(this DatabaseFacade databaseFacade) + { + var cosmosOptions = databaseFacade.GetService().FindExtension(); + if (cosmosOptions == null) + { + throw new InvalidOperationException(CosmosStrings.CosmosNotInUse); + } + + return cosmosOptions.DatabaseName; + } + /// /// Returns if the database provider currently in use is the Cosmos provider. /// diff --git a/src/EFCore.Cosmos/Extensions/CosmosDbContextOptionsExtensions.cs b/src/EFCore.Cosmos/Extensions/CosmosDbContextOptionsExtensions.cs index b5e2d4ba984..67783d060d3 100644 --- a/src/EFCore.Cosmos/Extensions/CosmosDbContextOptionsExtensions.cs +++ b/src/EFCore.Cosmos/Extensions/CosmosDbContextOptionsExtensions.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Azure.Core; using Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal; // ReSharper disable once CheckNamespace @@ -83,6 +84,74 @@ public static DbContextOptionsBuilder UseCosmos( return optionsBuilder; } + /// + /// Configures the context to connect to an Azure Cosmos database. + /// + /// + /// See Using DbContextOptions, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// The type of context to be configured. + /// The builder being used to configure the context. + /// The account end-point to connect to. + /// The Azure authentication token. + /// The database name. + /// An optional action to allow additional Cosmos-specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseCosmos( + this DbContextOptionsBuilder optionsBuilder, + string accountEndpoint, + TokenCredential tokenCredential, + string databaseName, + Action? cosmosOptionsAction = null) + where TContext : DbContext + => (DbContextOptionsBuilder)UseCosmos( + (DbContextOptionsBuilder)optionsBuilder, + accountEndpoint, + tokenCredential, + databaseName, + cosmosOptionsAction); + + /// + /// Configures the context to connect to an Azure Cosmos database. + /// + /// + /// See Using DbContextOptions, and + /// Accessing Azure Cosmos DB with EF Core for more information and examples. + /// + /// The builder being used to configure the context. + /// The account end-point to connect to. + /// The Azure authentication token. + /// The database name. + /// An optional action to allow additional Cosmos-specific configuration. + /// The options builder so that further configuration can be chained. + public static DbContextOptionsBuilder UseCosmos( + this DbContextOptionsBuilder optionsBuilder, + string accountEndpoint, + TokenCredential tokenCredential, + string databaseName, + Action? cosmosOptionsAction = null) + { + Check.NotNull(optionsBuilder, nameof(optionsBuilder)); + Check.NotNull(accountEndpoint, nameof(accountEndpoint)); + Check.NotNull(tokenCredential, nameof(tokenCredential)); + Check.NotEmpty(databaseName, nameof(databaseName)); + + var extension = optionsBuilder.Options.FindExtension() + ?? new CosmosOptionsExtension(); + + extension = extension + .WithAccountEndpoint(accountEndpoint) + .WithTokenCredential(tokenCredential) + .WithDatabaseName(databaseName); + + ((IDbContextOptionsBuilderInfrastructure)optionsBuilder).AddOrUpdateExtension(extension); + + cosmosOptionsAction?.Invoke(new CosmosDbContextOptionsBuilder(optionsBuilder)); + + return optionsBuilder; + } + /// /// Configures the context to connect to an Azure Cosmos database. /// diff --git a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosDbOptionExtension.cs b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosDbOptionExtension.cs index 1a6259332a9..33c4c7588f0 100644 --- a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosDbOptionExtension.cs +++ b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosDbOptionExtension.cs @@ -4,6 +4,7 @@ using System.Globalization; using System.Net; using System.Text; +using Azure.Core; using Microsoft.EntityFrameworkCore.Cosmos.Internal; namespace Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal; @@ -18,6 +19,7 @@ public class CosmosOptionsExtension : IDbContextOptionsExtension { private string? _accountEndpoint; private string? _accountKey; + private TokenCredential? _tokenCredential; private string? _connectionString; private string? _databaseName; private string? _region; @@ -55,6 +57,7 @@ protected CosmosOptionsExtension(CosmosOptionsExtension copyFrom) { _accountEndpoint = copyFrom._accountEndpoint; _accountKey = copyFrom._accountKey; + _tokenCredential = copyFrom._tokenCredential; _databaseName = copyFrom._databaseName; _connectionString = copyFrom._connectionString; _region = copyFrom._region; @@ -138,6 +141,35 @@ public virtual CosmosOptionsExtension WithAccountKey(string? accountKey) return clone; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TokenCredential? TokenCredential + => _tokenCredential; + + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual CosmosOptionsExtension WithTokenCredential(TokenCredential? tokenCredential) + { + if (tokenCredential is not null && _connectionString is not null) + { + throw new InvalidOperationException(CosmosStrings.ConnectionStringConflictingConfiguration); + } + + var clone = Clone(); + + clone._tokenCredential = tokenCredential; + + return clone; + } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -155,7 +187,7 @@ public virtual string? ConnectionString /// public virtual CosmosOptionsExtension WithConnectionString(string? connectionString) { - if (connectionString is not null && (_accountEndpoint != null || _accountKey != null)) + if (connectionString is not null && (_accountEndpoint != null || _accountKey != null || _tokenCredential != null)) { throw new InvalidOperationException(CosmosStrings.ConnectionStringConflictingConfiguration); } @@ -565,6 +597,7 @@ public override int GetServiceProviderHashCode() { hashCode.Add(Extension._accountEndpoint); hashCode.Add(Extension._accountKey); + hashCode.Add(Extension._tokenCredential); } hashCode.Add(Extension._region); @@ -591,6 +624,7 @@ public override bool ShouldUseSameServiceProvider(DbContextOptionsExtensionInfo && Extension._connectionString == otherInfo.Extension._connectionString && Extension._accountEndpoint == otherInfo.Extension._accountEndpoint && Extension._accountKey == otherInfo.Extension._accountKey + && Extension._tokenCredential == otherInfo.Extension._tokenCredential && Extension._region == otherInfo.Extension._region && Extension._connectionMode == otherInfo.Extension._connectionMode && Extension._limitToEndpoint == otherInfo.Extension._limitToEndpoint @@ -615,8 +649,17 @@ public override void PopulateDebugInfo(IDictionary debugInfo) { debugInfo["Cosmos:" + nameof(AccountEndpoint)] = (Extension._accountEndpoint?.GetHashCode() ?? 0L).ToString(CultureInfo.InvariantCulture); - debugInfo["Cosmos:" + nameof(AccountKey)] = - (Extension._accountKey?.GetHashCode() ?? 0L).ToString(CultureInfo.InvariantCulture); + + if (Extension._accountKey == null) + { + debugInfo["Cosmos:" + nameof(TokenCredential)] = + (Extension._tokenCredential?.GetHashCode() ?? 0L).ToString(CultureInfo.InvariantCulture); + } + else + { + debugInfo["Cosmos:" + nameof(AccountKey)] = + (Extension._accountKey?.GetHashCode() ?? 0L).ToString(CultureInfo.InvariantCulture); + } } debugInfo["Cosmos:" + nameof(CosmosDbContextOptionsBuilder.Region)] = diff --git a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosSingletonOptions.cs b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosSingletonOptions.cs index aba64b9ca85..22f657166c6 100644 --- a/src/EFCore.Cosmos/Infrastructure/Internal/CosmosSingletonOptions.cs +++ b/src/EFCore.Cosmos/Infrastructure/Internal/CosmosSingletonOptions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Net; +using Azure.Core; namespace Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal; @@ -29,6 +30,14 @@ public class CosmosSingletonOptions : ICosmosSingletonOptions /// public virtual string? AccountKey { get; private set; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + public virtual TokenCredential? TokenCredential { get; private set; } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in @@ -146,6 +155,7 @@ public virtual void Initialize(IDbContextOptions options) { AccountEndpoint = cosmosOptions.AccountEndpoint; AccountKey = cosmosOptions.AccountKey; + TokenCredential = cosmosOptions.TokenCredential; ConnectionString = cosmosOptions.ConnectionString; Region = cosmosOptions.Region; LimitToEndpoint = cosmosOptions.LimitToEndpoint; @@ -175,6 +185,7 @@ public virtual void Validate(IDbContextOptions options) if (cosmosOptions != null && (AccountEndpoint != cosmosOptions.AccountEndpoint || AccountKey != cosmosOptions.AccountKey + || TokenCredential != cosmosOptions.TokenCredential || ConnectionString != cosmosOptions.ConnectionString || Region != cosmosOptions.Region || LimitToEndpoint != cosmosOptions.LimitToEndpoint diff --git a/src/EFCore.Cosmos/Infrastructure/Internal/ICosmosSingletonOptions.cs b/src/EFCore.Cosmos/Infrastructure/Internal/ICosmosSingletonOptions.cs index 9aaa035affb..b38e4f40993 100644 --- a/src/EFCore.Cosmos/Infrastructure/Internal/ICosmosSingletonOptions.cs +++ b/src/EFCore.Cosmos/Infrastructure/Internal/ICosmosSingletonOptions.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Net; +using Azure.Core; namespace Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal; @@ -35,6 +36,14 @@ public interface ICosmosSingletonOptions : ISingletonOptions /// string? AccountKey { get; } + /// + /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to + /// the same compatibility standards as public APIs. It may be changed or removed without notice in + /// any release. You should only use it directly in your code with extreme caution and knowing that + /// doing so can result in application failures when updating to a new Entity Framework Core release. + /// + TokenCredential? TokenCredential { get; } + /// /// This is an internal API that supports the Entity Framework Core infrastructure and not subject to /// the same compatibility standards as public APIs. It may be changed or removed without notice in diff --git a/src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosConventionSetBuilder.cs b/src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosConventionSetBuilder.cs index a651c136622..46e8eeda1a6 100644 --- a/src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosConventionSetBuilder.cs +++ b/src/EFCore.Cosmos/Metadata/Conventions/Internal/CosmosConventionSetBuilder.cs @@ -36,7 +36,7 @@ public override ConventionSet CreateConventionSet() conventionSet.Add(new ContextContainerConvention(Dependencies)); conventionSet.Add(new ETagPropertyConvention()); conventionSet.Add(new StoreKeyConvention(Dependencies)); - + conventionSet.Replace(new CosmosValueGenerationConvention(Dependencies)); conventionSet.Replace(new CosmosKeyDiscoveryConvention(Dependencies)); conventionSet.Replace(new CosmosInversePropertyAttributeConvention(Dependencies)); diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs index 13136ad55ae..9523f38b1be 100644 --- a/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs +++ b/src/EFCore.Cosmos/Properties/CosmosStrings.Designer.cs @@ -38,7 +38,7 @@ public static string CanConnectNotSupported => GetString("CanConnectNotSupported"); /// - /// Both the connection string and account key or account endpoint were specified. Specify only one set of connection details. + /// Both the connection string and CredentialToken, account key or account endpoint were specified. Specify only one set of connection details. /// public static string ConnectionStringConflictingConfiguration => GetString("ConnectionStringConflictingConfiguration"); diff --git a/src/EFCore.Cosmos/Properties/CosmosStrings.resx b/src/EFCore.Cosmos/Properties/CosmosStrings.resx index f05458c6f77..99ccab5273c 100644 --- a/src/EFCore.Cosmos/Properties/CosmosStrings.resx +++ b/src/EFCore.Cosmos/Properties/CosmosStrings.resx @@ -124,7 +124,7 @@ The Cosmos database does not support 'CanConnect' or 'CanConnectAsync'. - Both the connection string and account key or account endpoint were specified. Specify only one set of connection details. + Both the connection string and CredentialToken, account key or account endpoint were specified. Specify only one set of connection details. Cosmos-specific methods can only be used when the context is using the Cosmos provider. diff --git a/src/EFCore.Cosmos/Storage/Internal/SingletonCosmosClientWrapper.cs b/src/EFCore.Cosmos/Storage/Internal/SingletonCosmosClientWrapper.cs index ab44298863a..bc4f620c922 100644 --- a/src/EFCore.Cosmos/Storage/Internal/SingletonCosmosClientWrapper.cs +++ b/src/EFCore.Cosmos/Storage/Internal/SingletonCosmosClientWrapper.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Azure.Core; using Microsoft.EntityFrameworkCore.Cosmos.Infrastructure.Internal; namespace Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal; @@ -18,6 +19,7 @@ public class SingletonCosmosClientWrapper : ISingletonCosmosClientWrapper private readonly string? _endpoint; private readonly string? _key; private readonly string? _connectionString; + private readonly TokenCredential? _tokenCredential; private CosmosClient? _client; /// @@ -31,6 +33,7 @@ public SingletonCosmosClientWrapper(ICosmosSingletonOptions options) _endpoint = options.AccountEndpoint; _key = options.AccountKey; _connectionString = options.ConnectionString; + _tokenCredential = options.TokenCredential; var configuration = new CosmosClientOptions { ApplicationName = UserAgent, Serializer = new JsonCosmosSerializer() }; if (options.Region != null) @@ -99,7 +102,9 @@ public SingletonCosmosClientWrapper(ICosmosSingletonOptions options) /// public virtual CosmosClient Client => _client ??= string.IsNullOrEmpty(_connectionString) - ? new CosmosClient(_endpoint, _key, _options) + ? _tokenCredential == null + ? new CosmosClient(_endpoint, _key, _options) + : new CosmosClient(_endpoint, _tokenCredential, _options) : new CosmosClient(_connectionString, _options); /// diff --git a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs index 98e7a936cf3..b89f188eff5 100644 --- a/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs +++ b/src/EFCore.Relational/Metadata/Conventions/Infrastructure/RelationalConventionSetBuilder.cs @@ -80,9 +80,9 @@ public override ConventionSet CreateConventionSet() conventionSet.Add(new RelationalMapToJsonConvention(Dependencies, RelationalDependencies)); conventionSet.Replace( - new RelationalValueGenerationConvention(Dependencies, RelationalDependencies)); + new RelationalValueGenerationConvention(Dependencies, RelationalDependencies)); conventionSet.Replace( - new RelationalQueryFilterRewritingConvention(Dependencies, RelationalDependencies)); + new RelationalQueryFilterRewritingConvention(Dependencies, RelationalDependencies)); conventionSet.Replace(new RelationalRuntimeModelConvention(Dependencies, RelationalDependencies)); return conventionSet; diff --git a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs index e3cef7b1da7..4ac397efd93 100644 --- a/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs +++ b/src/EFCore.SqlServer/Metadata/Conventions/SqlServerConventionSetBuilder.cs @@ -61,7 +61,7 @@ public override ConventionSet CreateConventionSet() conventionSet.Replace( new SqlServerValueGenerationConvention(Dependencies, RelationalDependencies)); conventionSet.Replace(new SqlServerRuntimeModelConvention(Dependencies, RelationalDependencies)); - + var sqlServerTemporalConvention = new SqlServerTemporalConvention(Dependencies, RelationalDependencies); ConventionSet.AddBefore( conventionSet.EntityTypeAnnotationChangedConventions, @@ -69,7 +69,7 @@ public override ConventionSet CreateConventionSet() typeof(SqlServerValueGenerationConvention)); conventionSet.SkipNavigationForeignKeyChangedConventions.Add(sqlServerTemporalConvention); conventionSet.ModelFinalizingConventions.Add(sqlServerTemporalConvention); - + return conventionSet; } diff --git a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs index d9f5fd95ca4..dc89fc504d8 100644 --- a/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs +++ b/src/EFCore/Metadata/Conventions/Infrastructure/ProviderConventionSetBuilder.cs @@ -53,7 +53,7 @@ public ProviderConventionSetBuilder(ProviderConventionSetBuilderDependencies dep public virtual ConventionSet CreateConventionSet() { var conventionSet = new ConventionSet(); - + conventionSet.Add(new ModelCleanupConvention(Dependencies)); conventionSet.Add(new NotMappedEntityTypeAttributeConvention(Dependencies)); conventionSet.Add(new OwnedEntityTypeAttributeConvention(Dependencies)); diff --git a/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs b/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs index d5f043b075d..a0cdb322fbe 100644 --- a/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs +++ b/src/EFCore/Metadata/Conventions/NavigationAttributeConventionBase.cs @@ -29,7 +29,7 @@ protected NavigationAttributeConventionBase(ProviderConventionSetBuilderDependen /// Dependencies for this service. /// protected virtual ProviderConventionSetBuilderDependencies Dependencies { get; } - + /// /// Called after an entity type is added to the model. /// @@ -59,7 +59,7 @@ public virtual void ProcessEntityTypeAdded( } } } - + /// /// Called after an entity type is ignored. /// @@ -111,7 +111,7 @@ public virtual void ProcessEntityTypeIgnored( } } } - + /// /// Called after an entity type is removed from the model. /// @@ -143,7 +143,7 @@ public virtual void ProcessEntityTypeRemoved( } } } - + /// /// Called after the base type of an entity type changes. /// @@ -211,7 +211,7 @@ public virtual void ProcessEntityTypeBaseTypeChanged( private static void Sort(List<(PropertyInfo, Type)> navigations) => navigations.Sort((x, y) => StringComparer.Ordinal.Compare(x.Item1.Name, y.Item1.Name)); - + /// /// Called after a navigation is added to the entity type. /// @@ -232,7 +232,7 @@ public virtual void ProcessNavigationAdded( } } } - + /// /// Called after a skip navigation is added to the entity type. /// @@ -253,7 +253,7 @@ public virtual void ProcessSkipNavigationAdded( } } } - + /// /// Called after the principal end of a foreign key is changed. /// @@ -273,7 +273,7 @@ public virtual void ProcessForeignKeyPrincipalEndChanged( ProcessForeignKeyPrincipalEndChanged( relationshipBuilder, dependentToPrincipalAttributes, principalToDependentAttributes, context); } - + /// /// Called after an entity type member is ignored. /// diff --git a/test/EFCore.Cosmos.FunctionalTests/ConfigPatternsCosmosTest.cs b/test/EFCore.Cosmos.FunctionalTests/ConfigPatternsCosmosTest.cs index 90ac7135e9c..32ecbdeb026 100644 --- a/test/EFCore.Cosmos.FunctionalTests/ConfigPatternsCosmosTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/ConfigPatternsCosmosTest.cs @@ -28,6 +28,7 @@ public async Task Cosmos_client_instance_is_shared_between_contexts() { client = context.Database.GetCosmosClient(); Assert.NotNull(client); + Assert.Equal(DatabaseName, context.Database.GetCosmosDatabaseId()); Assert.True(context.Database.IsCosmos()); } diff --git a/test/EFCore.Cosmos.FunctionalTests/ConnectionSpecificationTest.cs b/test/EFCore.Cosmos.FunctionalTests/ConnectionSpecificationTest.cs index 6fea7cd62a4..2af29c8abb0 100644 --- a/test/EFCore.Cosmos.FunctionalTests/ConnectionSpecificationTest.cs +++ b/test/EFCore.Cosmos.FunctionalTests/ConnectionSpecificationTest.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Azure.Core; using Microsoft.EntityFrameworkCore.Cosmos.Internal; namespace Microsoft.EntityFrameworkCore.Cosmos; @@ -38,6 +39,43 @@ protected override void OnModelCreating(ModelBuilder modelBuilder) public DbSet Blogs { get; set; } } + [ConditionalFact] + public async Task Specifying_connection_string_and_TokenCredential_throws() + { + await using var testDatabase = CosmosTestStore.Create("NonExisting"); + using var context = new BloggingContextWithTokenCredential(testDatabase); + + Assert.Equal( + CosmosStrings.ConnectionStringConflictingConfiguration, + Assert.Throws(() => context.GetService()).Message); + } + + public class BloggingContextWithTokenCredential : DbContext + { + private readonly string _connectionString; + private readonly string _connectionUri; + private readonly TokenCredential _tokenCredential; + private readonly string _name; + + public BloggingContextWithTokenCredential(CosmosTestStore testStore) + { + _connectionString = testStore.ConnectionString; + _connectionUri = testStore.ConnectionUri; + _tokenCredential = testStore.TokenCredential; + _name = testStore.Name; + } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + => optionsBuilder.UseCosmos(_connectionString, _name, b => b.ApplyConfiguration()) + .UseCosmos(_connectionUri, _tokenCredential, _name, b => b.ApplyConfiguration()); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + } + + public DbSet Blogs { get; set; } + } + [ConditionalFact] public async Task Specifying_connection_string_and_account_endpoint_throws() { diff --git a/test/EFCore.Cosmos.FunctionalTests/EFCore.Cosmos.FunctionalTests.csproj b/test/EFCore.Cosmos.FunctionalTests/EFCore.Cosmos.FunctionalTests.csproj index eb810463022..62d13aa607a 100644 --- a/test/EFCore.Cosmos.FunctionalTests/EFCore.Cosmos.FunctionalTests.csproj +++ b/test/EFCore.Cosmos.FunctionalTests/EFCore.Cosmos.FunctionalTests.csproj @@ -56,6 +56,7 @@ + diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs index 45a84471177..57cb3288778 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/CosmosTestStore.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Net.Sockets; +using Azure.Core; using Microsoft.Azure.Cosmos; using Microsoft.EntityFrameworkCore.Cosmos.Storage.Internal; using Newtonsoft.Json; @@ -42,6 +43,7 @@ private CosmosTestStore( ConnectionUri = TestEnvironment.DefaultConnection; AuthToken = TestEnvironment.AuthToken; ConnectionString = TestEnvironment.ConnectionString; + TokenCredential = TestEnvironment.TokenCredential; _configureCosmos = extensionConfiguration == null ? b => b.ApplyConfiguration() : b => @@ -67,6 +69,7 @@ private static string CreateName(string name) public string ConnectionUri { get; } public string AuthToken { get; } + public TokenCredential TokenCredential { get; } public string ConnectionString { get; } protected override DbContext CreateDefaultContext() diff --git a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/TestEnvironment.cs b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/TestEnvironment.cs index 13d7b1abb66..e817610a84e 100644 --- a/test/EFCore.Cosmos.FunctionalTests/TestUtilities/TestEnvironment.cs +++ b/test/EFCore.Cosmos.FunctionalTests/TestUtilities/TestEnvironment.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using Azure.Core; +using Azure.Identity; using Microsoft.Extensions.Configuration; namespace Microsoft.EntityFrameworkCore.TestUtilities; @@ -25,5 +27,7 @@ public static class TestEnvironment public static string ConnectionString { get; } = $"AccountEndpoint={DefaultConnection};AccountKey={AuthToken}"; + public static TokenCredential TokenCredential { get; } = new DefaultAzureCredential(); + public static bool IsEmulator { get; } = DefaultConnection.StartsWith("https://localhost:8081", StringComparison.Ordinal); } diff --git a/test/EFCore.Design.Tests/Scaffolding/Internal/ModelCodeGeneratorTestBase.cs b/test/EFCore.Design.Tests/Scaffolding/Internal/ModelCodeGeneratorTestBase.cs index aabc661e31d..cb575160ec4 100644 --- a/test/EFCore.Design.Tests/Scaffolding/Internal/ModelCodeGeneratorTestBase.cs +++ b/test/EFCore.Design.Tests/Scaffolding/Internal/ModelCodeGeneratorTestBase.cs @@ -3,7 +3,6 @@ using System.Runtime.InteropServices; using Microsoft.EntityFrameworkCore.Design.Internal; -using Microsoft.EntityFrameworkCore.Metadata.Internal; namespace Microsoft.EntityFrameworkCore.Scaffolding.Internal;