From a9a4ea0d19f95312bbaf5078761ab6ebeaccb450 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Thu, 22 Jan 2026 13:37:51 -0800 Subject: [PATCH 01/14] Changes for autoentities --- src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs index 1cadaf5838..f43553b8fb 100644 --- a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs +++ b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs @@ -60,6 +60,7 @@ public override Type SqlToCLRType(string sqlType) /// public override async Task PopulateTriggerMetadataForTable(string entityName, string schemaName, string tableName, SourceDefinition sourceDefinition) { + string enumerateEnabledTriggers = SqlQueryBuilder.BuildFetchEnabledTriggersQuery(); Dictionary parameters = new() { From 4acee5c09b1bdfbc4773054642ba62402e53c339 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Mon, 26 Jan 2026 15:28:51 -0800 Subject: [PATCH 02/14] Add query and its execution --- .../MetadataProviders/MsSqlMetadataProvider.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs index f43553b8fb..2433049b86 100644 --- a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs +++ b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs @@ -295,10 +295,17 @@ private bool TryResolveDbType(string sqlDbTypeName, out DbType dbType) /// protected override async Task GenerateAutoentitiesIntoEntities() { - await Task.CompletedTask; + RuntimeConfig runtimeConfig = _runtimeConfigProvider.GetConfig(); + if (runtimeConfig.Autoentities is not null) + { + foreach ((string name, Autoentity autoentity) in runtimeConfig.Autoentities.AutoEntities) + { + JsonArray? resultArray = await QueryAutoentitiesConfiguration(autoentity); + } + } } - public async Task QueryAutoentitiesAsync(Autoentity autoentity) + public async Task QueryAutoentitiesConfiguration(Autoentity autoentity) { string include = string.Join(",", autoentity.Patterns.Include); string exclude = string.Join(",", autoentity.Patterns.Exclude); From 67e10d77d7094090f549041c788a91eabddce329 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Mon, 26 Jan 2026 21:26:04 -0800 Subject: [PATCH 03/14] Add testing --- src/Service.Tests/UnitTests/SqlMetadataProviderUnitTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Service.Tests/UnitTests/SqlMetadataProviderUnitTests.cs b/src/Service.Tests/UnitTests/SqlMetadataProviderUnitTests.cs index 30969918c0..3aa9f465e3 100644 --- a/src/Service.Tests/UnitTests/SqlMetadataProviderUnitTests.cs +++ b/src/Service.Tests/UnitTests/SqlMetadataProviderUnitTests.cs @@ -19,6 +19,7 @@ using Azure.DataApiBuilder.Service.Exceptions; using Azure.DataApiBuilder.Service.Tests.Configuration; using Azure.DataApiBuilder.Service.Tests.SqlTests; +using HotChocolate.Execution.Processing; using Microsoft.AspNetCore.Http; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; From 5453cbce10b86f4d13c6532911063d932c34514c Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Wed, 28 Jan 2026 16:49:13 -0800 Subject: [PATCH 04/14] Changes based on comments --- src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs | 1 + src/Service.Tests/UnitTests/SqlMetadataProviderUnitTests.cs | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs index 2433049b86..5002c2acb5 100644 --- a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs +++ b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs @@ -301,6 +301,7 @@ protected override async Task GenerateAutoentitiesIntoEntities() foreach ((string name, Autoentity autoentity) in runtimeConfig.Autoentities.AutoEntities) { JsonArray? resultArray = await QueryAutoentitiesConfiguration(autoentity); + // TODO: Finish implementation of autoentities generation in task #3052 } } } diff --git a/src/Service.Tests/UnitTests/SqlMetadataProviderUnitTests.cs b/src/Service.Tests/UnitTests/SqlMetadataProviderUnitTests.cs index 3aa9f465e3..30969918c0 100644 --- a/src/Service.Tests/UnitTests/SqlMetadataProviderUnitTests.cs +++ b/src/Service.Tests/UnitTests/SqlMetadataProviderUnitTests.cs @@ -19,7 +19,6 @@ using Azure.DataApiBuilder.Service.Exceptions; using Azure.DataApiBuilder.Service.Tests.Configuration; using Azure.DataApiBuilder.Service.Tests.SqlTests; -using HotChocolate.Execution.Processing; using Microsoft.AspNetCore.Http; using Microsoft.Data.SqlClient; using Microsoft.Extensions.Logging; From 8fbea755c5152f89fff63ca074e99f9ff8971aea Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Wed, 28 Jan 2026 17:26:57 -0800 Subject: [PATCH 05/14] Changes based on comments --- .../Services/MetadataProviders/MsSqlMetadataProvider.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs index 5002c2acb5..b815216d39 100644 --- a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs +++ b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs @@ -300,13 +300,13 @@ protected override async Task GenerateAutoentitiesIntoEntities() { foreach ((string name, Autoentity autoentity) in runtimeConfig.Autoentities.AutoEntities) { - JsonArray? resultArray = await QueryAutoentitiesConfiguration(autoentity); + JsonArray? resultArray = await QueryAutoentitiesAsync(autoentity); // TODO: Finish implementation of autoentities generation in task #3052 } } } - public async Task QueryAutoentitiesConfiguration(Autoentity autoentity) + public async Task QueryAutoentitiesAsync(Autoentity autoentity) { string include = string.Join(",", autoentity.Patterns.Include); string exclude = string.Join(",", autoentity.Patterns.Exclude); @@ -324,6 +324,11 @@ protected override async Task GenerateAutoentitiesIntoEntities() _logger.LogInformation($"Autoentities exclude pattern: {exclude}"); _logger.LogInformation($"Autoentities name pattern: {namePattern}"); + _logger.LogInformation("Query for Autoentities is being executed with the following parameters."); + _logger.LogInformation($"Autoentities include pattern: {include}"); + _logger.LogInformation($"Autoentities exclude pattern: {exclude}"); + _logger.LogInformation($"Autoentities name pattern: {namePattern}"); + JsonArray? resultArray = await QueryExecutor.ExecuteQueryAsync( sqltext: getAutoentitiesQuery, parameters: parameters, From cf4debf1013b96dec6b826ca59fea0da1f43a2f2 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Thu, 29 Jan 2026 17:19:58 -0800 Subject: [PATCH 06/14] Added changes based on comments --- src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs index b815216d39..ebea0c0992 100644 --- a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs +++ b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs @@ -324,11 +324,6 @@ protected override async Task GenerateAutoentitiesIntoEntities() _logger.LogInformation($"Autoentities exclude pattern: {exclude}"); _logger.LogInformation($"Autoentities name pattern: {namePattern}"); - _logger.LogInformation("Query for Autoentities is being executed with the following parameters."); - _logger.LogInformation($"Autoentities include pattern: {include}"); - _logger.LogInformation($"Autoentities exclude pattern: {exclude}"); - _logger.LogInformation($"Autoentities name pattern: {namePattern}"); - JsonArray? resultArray = await QueryExecutor.ExecuteQueryAsync( sqltext: getAutoentitiesQuery, parameters: parameters, From 88b8c662df4ad0200aa2494216bc70920997ef45 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Thu, 29 Jan 2026 17:24:38 -0800 Subject: [PATCH 07/14] Comment out failing section --- .../Services/MetadataProviders/MsSqlMetadataProvider.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs index ebea0c0992..fb49e0d33a 100644 --- a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs +++ b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs @@ -293,7 +293,8 @@ private bool TryResolveDbType(string sqlDbTypeName, out DbType dbType) } /// - protected override async Task GenerateAutoentitiesIntoEntities() + // TODO: Finish implementation of autoentities generation in task #3052 + /*protected override async Task GenerateAutoentitiesIntoEntities() { RuntimeConfig runtimeConfig = _runtimeConfigProvider.GetConfig(); if (runtimeConfig.Autoentities is not null) @@ -301,10 +302,9 @@ protected override async Task GenerateAutoentitiesIntoEntities() foreach ((string name, Autoentity autoentity) in runtimeConfig.Autoentities.AutoEntities) { JsonArray? resultArray = await QueryAutoentitiesAsync(autoentity); - // TODO: Finish implementation of autoentities generation in task #3052 } } - } + }*/ public async Task QueryAutoentitiesAsync(Autoentity autoentity) { From 612d4bdb97fe9a94aa0dc236c1ef46877ebe5288 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Thu, 29 Jan 2026 17:30:33 -0800 Subject: [PATCH 08/14] Fix test failures --- .../MetadataProviders/MsSqlMetadataProvider.cs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs index fb49e0d33a..a22452b2f1 100644 --- a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs +++ b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs @@ -294,17 +294,19 @@ private bool TryResolveDbType(string sqlDbTypeName, out DbType dbType) /// // TODO: Finish implementation of autoentities generation in task #3052 - /*protected override async Task GenerateAutoentitiesIntoEntities() + protected override async Task GenerateAutoentitiesIntoEntities() { - RuntimeConfig runtimeConfig = _runtimeConfigProvider.GetConfig(); + await Task.CompletedTask; // Temporary await to suppress build errors. + + /*RuntimeConfig runtimeConfig = _runtimeConfigProvider.GetConfig(); if (runtimeConfig.Autoentities is not null) { foreach ((string name, Autoentity autoentity) in runtimeConfig.Autoentities.AutoEntities) { JsonArray? resultArray = await QueryAutoentitiesAsync(autoentity); } - } - }*/ + }*/ + } public async Task QueryAutoentitiesAsync(Autoentity autoentity) { From a396fab79a60389e57807a93ae345280211ae736 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Thu, 29 Jan 2026 17:48:50 -0800 Subject: [PATCH 09/14] Fix formatting issues --- src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs index a22452b2f1..4e3be0548a 100644 --- a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs +++ b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs @@ -60,7 +60,6 @@ public override Type SqlToCLRType(string sqlType) /// public override async Task PopulateTriggerMetadataForTable(string entityName, string schemaName, string tableName, SourceDefinition sourceDefinition) { - string enumerateEnabledTriggers = SqlQueryBuilder.BuildFetchEnabledTriggersQuery(); Dictionary parameters = new() { From c8ed0d5d1c3ba6a439633416b85a0796a0355eb8 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Thu, 5 Feb 2026 09:52:16 -0800 Subject: [PATCH 10/14] Generate in-memory entities --- src/Config/ObjectModel/RuntimeConfig.cs | 5 ++ .../MsSqlMetadataProvider.cs | 68 +++++++++++++++++-- .../MetadataProviders/SqlMetadataProvider.cs | 3 +- 3 files changed, 68 insertions(+), 8 deletions(-) diff --git a/src/Config/ObjectModel/RuntimeConfig.cs b/src/Config/ObjectModel/RuntimeConfig.cs index 1e567da1cd..2f9a4a5e29 100644 --- a/src/Config/ObjectModel/RuntimeConfig.cs +++ b/src/Config/ObjectModel/RuntimeConfig.cs @@ -245,6 +245,11 @@ public bool TryGetEntityNameFromPath(string entityPathName, [NotNullWhen(true)] return _entityPathNameToEntityName.TryGetValue(entityPathName, out entityName); } + public bool TryAddEntityNameToDataSourceName(string entityName) + { + return _entityNameToDataSourceName.TryAdd(entityName, this.DefaultDataSourceName); + } + /// /// Constructor for runtimeConfig. /// To be used when setting up from cli json scenario. diff --git a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs index 4e3be0548a..8c27c91650 100644 --- a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs +++ b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs @@ -292,19 +292,73 @@ private bool TryResolveDbType(string sqlDbTypeName, out DbType dbType) } /// - // TODO: Finish implementation of autoentities generation in task #3052 protected override async Task GenerateAutoentitiesIntoEntities() { - await Task.CompletedTask; // Temporary await to suppress build errors. + RuntimeConfig runtimeConfig = _runtimeConfigProvider.GetConfig(); + Dictionary entities = (Dictionary)_entities; + if (runtimeConfig.Autoentities is null) + { + return; + } - /*RuntimeConfig runtimeConfig = _runtimeConfigProvider.GetConfig(); - if (runtimeConfig.Autoentities is not null) + foreach ((string autoentityName, Autoentity autoentity) in runtimeConfig.Autoentities.AutoEntities) { - foreach ((string name, Autoentity autoentity) in runtimeConfig.Autoentities.AutoEntities) + JsonArray? resultArray = await QueryAutoentitiesAsync(autoentity); + if (resultArray is null) { - JsonArray? resultArray = await QueryAutoentitiesAsync(autoentity); + continue; } - }*/ + + foreach (JsonObject resultObject in resultArray!) + { + // Extract the entity name, schema, and database object name from the query result. + // The SQL query returns these values with placeholders already replaced. + + // TODO: change it so that we don't need to verify if the names are null + string? entityName = resultObject["entity_name"]?.GetValue(); + string? schemaName = resultObject["schema"]?.GetValue(); + string? objectName = resultObject["object"]?.GetValue(); + + if (string.IsNullOrWhiteSpace(entityName) || string.IsNullOrWhiteSpace(objectName)) + { + _logger.LogError("Skipping autoentity generation: entity_name or object is null or empty for autoentity pattern '{AutoentityName}'.", autoentityName); + continue; + } + + // Create the entity using the template settings and permissions from the autoentity configuration. + // Currently the source type is always Table for auto-generated entities from database objects. + Entity generatedEntity = new( + Source: new EntitySource( + Object: objectName, + Type: EntitySourceType.Table, + Parameters: null, + KeyFields: null), + GraphQL: autoentity.Template.GraphQL, + Rest: autoentity.Template.Rest, + Mcp: autoentity.Template.Mcp, + Permissions: autoentity.Permissions, + Cache: autoentity.Template.Cache, + Health: autoentity.Template.Health, + Fields: null, + Relationships: null, + Mappings: new()); + + // Add the generated entity to the linking entities dictionary. + // This allows the entity to be processed later during metadata population. + // TODO: Add new log message that shows if the rest calls are enabled or disabled for each of this new entities + if (!entities.TryAdd(entityName, generatedEntity) || !runtimeConfig.TryAddEntityNameToDataSourceName(entityName)) + { + // TODO: need to make a better message that includes if the conflict is with a user-defined entity or another auto-generated entity. + throw new DataApiBuilderException( + message: $"Entity with name '{entityName}' already exists. Cannot create new entity from autoentity pattern '{autoentityName}'.", + statusCode: HttpStatusCode.BadRequest, + subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorInInitialization); + } + } + } + + // TODO: include warning message that no autoentities were created if it is the same number between the _entities and the new entities. + _entities = entities; } public async Task QueryAutoentitiesAsync(Autoentity autoentity) diff --git a/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs index c9a62ca470..bf729a1d78 100644 --- a/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs +++ b/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs @@ -39,7 +39,7 @@ public abstract class SqlMetadataProvider : private readonly DatabaseType _databaseType; // Represents the entities exposed in the runtime config. - private IReadOnlyDictionary _entities; + protected IReadOnlyDictionary _entities; // Represents the linking entities created by DAB to support multiple mutations for entities having an M:N relationship between them. protected Dictionary _linkingEntities = new(); @@ -307,6 +307,7 @@ public string GetEntityName(string graphQLType) public async Task InitializeAsync() { System.Diagnostics.Stopwatch timer = System.Diagnostics.Stopwatch.StartNew(); + if (GetDatabaseType() == DatabaseType.MSSQL) { await GenerateAutoentitiesIntoEntities(); From 947fdecfe796607cfb2e30f739bd8ea1abb70cf0 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Tue, 10 Feb 2026 17:01:11 -0800 Subject: [PATCH 11/14] Changes to generate autoentities as entities --- src/Config/ObjectModel/RuntimeConfig.cs | 19 ++- src/Config/RuntimeConfigLoader.cs | 5 + .../Configurations/RuntimeConfigProvider.cs | 9 ++ .../Configurations/RuntimeConfigValidator.cs | 2 +- .../MsSqlMetadataProvider.cs | 34 ++-- .../Configuration/ConfigurationTests.cs | 151 ++++++++++++++++++ src/Service/Startup.cs | 2 + 7 files changed, 204 insertions(+), 18 deletions(-) diff --git a/src/Config/ObjectModel/RuntimeConfig.cs b/src/Config/ObjectModel/RuntimeConfig.cs index 2f9a4a5e29..55de26c99d 100644 --- a/src/Config/ObjectModel/RuntimeConfig.cs +++ b/src/Config/ObjectModel/RuntimeConfig.cs @@ -273,7 +273,7 @@ public RuntimeConfig( this.DataSource = DataSource; this.Runtime = Runtime; this.AzureKeyVault = AzureKeyVault; - this.Entities = Entities; + this.Entities = Entities ?? new RuntimeEntities(new Dictionary()); this.Autoentities = Autoentities; this.DefaultDataSourceName = Guid.NewGuid().ToString(); @@ -292,17 +292,20 @@ public RuntimeConfig( }; _entityNameToDataSourceName = new Dictionary(); - if (Entities is null) + if (Entities is null && Autoentities is null) { throw new DataApiBuilderException( - message: "entities is a mandatory property in DAB Config", + message: "Configuration file should contain either at least the Entities or Autoentities property", statusCode: HttpStatusCode.UnprocessableEntity, subStatusCode: DataApiBuilderException.SubStatusCodes.ConfigValidationError); } - foreach (KeyValuePair entity in Entities) + if (Entities is not null) { - _entityNameToDataSourceName.TryAdd(entity.Key, this.DefaultDataSourceName); + foreach (KeyValuePair entity in Entities) + { + _entityNameToDataSourceName.TryAdd(entity.Key, this.DefaultDataSourceName); + } } // Process data source and entities information for each database in multiple database scenario. @@ -310,7 +313,7 @@ public RuntimeConfig( if (DataSourceFiles is not null && DataSourceFiles.SourceFiles is not null) { - IEnumerable> allEntities = Entities.AsEnumerable(); + IEnumerable>? allEntities = Entities?.AsEnumerable(); // Iterate through all the datasource files and load the config. IFileSystem fileSystem = new FileSystem(); // This loader is not used as a part of hot reload and therefore does not need a handler. @@ -327,7 +330,7 @@ public RuntimeConfig( { _dataSourceNameToDataSource = _dataSourceNameToDataSource.Concat(config._dataSourceNameToDataSource).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); _entityNameToDataSourceName = _entityNameToDataSourceName.Concat(config._entityNameToDataSourceName).ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - allEntities = allEntities.Concat(config.Entities.AsEnumerable()); + allEntities = allEntities?.Concat(config.Entities.AsEnumerable()); } catch (Exception e) { @@ -341,7 +344,7 @@ public RuntimeConfig( } } - this.Entities = new RuntimeEntities(allEntities.ToDictionary(x => x.Key, x => x.Value)); + this.Entities = new RuntimeEntities(allEntities != null ? allEntities.ToDictionary(x => x.Key, x => x.Value) : new Dictionary()); } SetupDataSourcesUsed(); diff --git a/src/Config/RuntimeConfigLoader.cs b/src/Config/RuntimeConfigLoader.cs index 8939f34e21..ae5c2dde95 100644 --- a/src/Config/RuntimeConfigLoader.cs +++ b/src/Config/RuntimeConfigLoader.cs @@ -499,4 +499,9 @@ public void InsertWantedChangesInProductionMode() RuntimeConfig = runtimeConfigCopy; } } + + public void EditRuntimeConfig(RuntimeConfig newRuntimeConfig) + { + RuntimeConfig = newRuntimeConfig; + } } diff --git a/src/Core/Configurations/RuntimeConfigProvider.cs b/src/Core/Configurations/RuntimeConfigProvider.cs index b46a716f48..79f717529e 100644 --- a/src/Core/Configurations/RuntimeConfigProvider.cs +++ b/src/Core/Configurations/RuntimeConfigProvider.cs @@ -411,4 +411,13 @@ private static RuntimeConfig HandleCosmosNoSqlConfiguration(string? schema, Runt return runtimeConfig; } + + public void AddNewEntitiesToConfig(Dictionary entities) + { + RuntimeConfig newRuntimeConfig = _configLoader.RuntimeConfig! with + { + Entities = new(entities) + }; + _configLoader.EditRuntimeConfig(newRuntimeConfig); + } } diff --git a/src/Core/Configurations/RuntimeConfigValidator.cs b/src/Core/Configurations/RuntimeConfigValidator.cs index 208c6fb331..009339bb5f 100644 --- a/src/Core/Configurations/RuntimeConfigValidator.cs +++ b/src/Core/Configurations/RuntimeConfigValidator.cs @@ -602,7 +602,7 @@ public void ValidateEntityConfiguration(RuntimeConfig runtimeConfig) // Stores the unique rest paths configured for different entities present in the config. HashSet restPathsForEntities = new(); - foreach ((string entityName, Entity entity) in runtimeConfig.Entities) + foreach ((string entityName, Entity entity) in runtimeConfig.Entities ?? Enumerable.Empty>()) { if (runtimeConfig.IsRestEnabled && entity.Rest is not null && entity.Rest.Enabled) { diff --git a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs index 8c27c91650..801d7bc493 100644 --- a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs +++ b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs @@ -6,6 +6,7 @@ using System.Net; using System.Text.Json; using System.Text.Json.Nodes; +using Azure.DataApiBuilder.Auth; using Azure.DataApiBuilder.Config.DatabasePrimitives; using Azure.DataApiBuilder.Config.ObjectModel; using Azure.DataApiBuilder.Core.Configurations; @@ -294,6 +295,7 @@ private bool TryResolveDbType(string sqlDbTypeName, out DbType dbType) /// protected override async Task GenerateAutoentitiesIntoEntities() { + int addedEntities = 0; RuntimeConfig runtimeConfig = _runtimeConfigProvider.GetConfig(); Dictionary entities = (Dictionary)_entities; if (runtimeConfig.Autoentities is null) @@ -313,11 +315,9 @@ protected override async Task GenerateAutoentitiesIntoEntities() { // Extract the entity name, schema, and database object name from the query result. // The SQL query returns these values with placeholders already replaced. - - // TODO: change it so that we don't need to verify if the names are null - string? entityName = resultObject["entity_name"]?.GetValue(); - string? schemaName = resultObject["schema"]?.GetValue(); - string? objectName = resultObject["object"]?.GetValue(); + string entityName = resultObject["entity_name"]!.ToString(); + string schemaName = resultObject["schema"]!.ToString(); + string objectName = resultObject["object"]!.ToString(); if (string.IsNullOrWhiteSpace(entityName) || string.IsNullOrWhiteSpace(objectName)) { @@ -345,20 +345,36 @@ protected override async Task GenerateAutoentitiesIntoEntities() // Add the generated entity to the linking entities dictionary. // This allows the entity to be processed later during metadata population. - // TODO: Add new log message that shows if the rest calls are enabled or disabled for each of this new entities if (!entities.TryAdd(entityName, generatedEntity) || !runtimeConfig.TryAddEntityNameToDataSourceName(entityName)) { // TODO: need to make a better message that includes if the conflict is with a user-defined entity or another auto-generated entity. throw new DataApiBuilderException( - message: $"Entity with name '{entityName}' already exists. Cannot create new entity from autoentity pattern '{autoentityName}'.", + message: $"Entity with name '{entityName}' already exists. Cannot create new entity from autoentity pattern with definition-name '{autoentityName}'.", statusCode: HttpStatusCode.BadRequest, subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorInInitialization); } + + if (runtimeConfig.IsRestEnabled) + { + _logger.LogInformation("[{entity}] REST path: {globalRestPath}/{entityRestPath}", entityName, runtimeConfig.RestPath, entityName); + } + else + { + _logger.LogInformation(message: "REST calls are disabled for the entity: {entity}", entityName); + } + + addedEntities++; } } - // TODO: include warning message that no autoentities were created if it is the same number between the _entities and the new entities. - _entities = entities; + if (addedEntities == 0) + { + _logger.LogWarning("No new entities were generated from the autoentity patterns defined in the configuration."); + } + else + { + _runtimeConfigProvider.AddNewEntitiesToConfig(entities); + } } public async Task QueryAutoentitiesAsync(Autoentity autoentity) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index 9df54be519..bc41f231e1 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -5231,6 +5231,157 @@ public async Task TestGraphQLIntrospectionQueriesAreNotImpactedByDepthLimit() } } + [TestCategory(TestCategory.MSSQL)] + [TestMethod] + public async Task TestAutoentitiesAreGeneratedIntoEntities() + { + // Arrange + Dictionary autoentityMap = new() + { + { + "PublisherAutoEntity", new Autoentity( + Patterns: new AutoentityPatterns( + Include: new[] { "%publishers%" }, + Exclude: null, + Name: null // Let DAB decide entity naming + ), + Template: new AutoentityTemplate( + Rest: new EntityRestOptions(Enabled: true), // Enable REST as requested + GraphQL: new EntityGraphQLOptions( + Singular: string.Empty, + Plural: string.Empty, + Enabled: true + ), + Health: null, + Cache: null + ), + Permissions: new[] { GetMinimalPermissionConfig(AuthorizationResolver.ROLE_ANONYMOUS) } + ) + } + }; + + // Create DataSource for MSSQL connection + DataSource dataSource = new(DatabaseType.MSSQL, + GetConnectionStringFromEnvironmentConfig(environment: TestCategory.MSSQL), Options: null); + + // Build complete runtime configuration with autoentities + RuntimeConfig configuration = new( + Schema: "TestAutoentitiesSchema", + DataSource: dataSource, + Runtime: new( + Rest: new(Enabled: true), + GraphQL: new(Enabled: true), + Mcp: new(Enabled: false), + Host: new( + Cors: null, + Authentication: new Config.ObjectModel.AuthenticationOptions( + Provider: nameof(EasyAuthType.StaticWebApps), + Jwt: null + ) + ) + ), + Entities: new(new Dictionary()), // Start with empty entities + Autoentities: new RuntimeAutoentities(autoentityMap) + ); + + const string CUSTOM_CONFIG = "autoentities-test-config.json"; + File.WriteAllText(CUSTOM_CONFIG, configuration.ToJson()); + + string[] args = new[] { $"--ConfigFileName={CUSTOM_CONFIG}" }; + + using (TestServer server = new(Program.CreateWebHostBuilder(args))) + using (HttpClient client = server.CreateClient()) + { + // Act + HttpRequestMessage restRequest = new(HttpMethod.Get, "/api/publishers"); + HttpResponseMessage restResponse = await client.SendAsync(restRequest); + + string graphqlQuery = @" + { + publishers { + items { + id + name + } + } + }"; + + object graphqlPayload = new { query = graphqlQuery }; + HttpRequestMessage graphqlRequest = new(HttpMethod.Post, "/graphql") + { + Content = JsonContent.Create(graphqlPayload) + }; + HttpResponseMessage graphqlResponse = await client.SendAsync(graphqlRequest); + + // Assert + // Verify REST response + Assert.AreEqual(HttpStatusCode.OK, restResponse.StatusCode, + "REST request to auto-generated entity should succeed"); + + // TODO: check this thing + string restResponseBody = await restResponse.Content.ReadAsStringAsync(); + Assert.IsFalse(string.IsNullOrEmpty(restResponseBody), + "REST response should contain data"); + + // Parse and validate REST response structure + JsonElement restJsonResponse = JsonSerializer.Deserialize(restResponseBody); + Assert.IsTrue(restJsonResponse.TryGetProperty("value", out JsonElement restDataArray), + "REST response should contain 'value' property with data array"); + Assert.AreEqual(JsonValueKind.Array, restDataArray.ValueKind, + "REST response data should be an array"); + + // TODO: Add specific field validation here based on your publishers table schema + // I think it can be done by sending a request to the database and get compare the values we get with this response + // Example: + // if (restDataArray.GetArrayLength() > 0) + // { + // JsonElement firstPublisher = restDataArray[0]; + // Assert.IsTrue(firstPublisher.TryGetProperty("id", out _), "Publisher should have id field"); + // Assert.IsTrue(firstPublisher.TryGetProperty("name", out _), "Publisher should have name field"); + // + // // Add more specific field and value assertions here + // } + + // Verify GraphQL response + Assert.AreEqual(HttpStatusCode.OK, graphqlResponse.StatusCode, + "GraphQL request to auto-generated entity should succeed"); + + // TODO: Check this thing, I think we can just check if it is null without having to do too much stuff with readasstringasync + string graphqlResponseBody = await graphqlResponse.Content.ReadAsStringAsync(); + Assert.IsFalse(string.IsNullOrEmpty(graphqlResponseBody), + "GraphQL response should contain data"); + + // Parse and validate GraphQL response structure + JsonElement graphqlJsonResponse = JsonSerializer.Deserialize(graphqlResponseBody); + Assert.IsFalse(graphqlJsonResponse.TryGetProperty("errors", out _), + "GraphQL response should not contain errors"); + Assert.IsTrue(graphqlJsonResponse.TryGetProperty("data", out JsonElement graphqlData), + "GraphQL response should contain data"); + Assert.IsTrue(graphqlData.TryGetProperty("publishers", out JsonElement publishersData), + "GraphQL data should contain publishers"); + Assert.IsTrue(publishersData.TryGetProperty("items", out JsonElement publishersItems), + "GraphQL publishers should contain items array"); + Assert.AreEqual(JsonValueKind.Array, publishersItems.ValueKind, + "GraphQL publishers items should be an array"); + + // TODO: Add specific GraphQL field validation here based on your publishers table schema + // Example: + // if (publishersItems.GetArrayLength() > 0) + // { + // JsonElement firstPublisher = publishersItems[0]; + // Assert.IsTrue(firstPublisher.TryGetProperty("id", out JsonElement idValue), "Publisher should have id field"); + // Assert.IsTrue(firstPublisher.TryGetProperty("name", out JsonElement nameValue), "Publisher should have name field"); + // + // // Add specific value assertions here: + // // Assert.AreEqual("Expected Publisher Name", nameValue.GetString()); + // // Assert.AreEqual(123, idValue.GetInt32()); + // } + + // Success! The autoentity was successfully converted to a working entity + // that responds to both REST and GraphQL requests + } + } + /// /// Tests the behavior of GraphQL queries in non-hosted mode when the depth limit is explicitly set to -1 or null. /// Setting the depth limit to -1 is intended to disable the depth limit check, allowing queries of any depth. diff --git a/src/Service/Startup.cs b/src/Service/Startup.cs index e16673347c..47e4471a79 100644 --- a/src/Service/Startup.cs +++ b/src/Service/Startup.cs @@ -1140,11 +1140,13 @@ private async Task PerformOnConfigChangeAsync(IApplicationBuilder app) // Now that the configuration has been set, perform validation of the runtime config // itself. + // TODO: Add this check at the end of generating the new entities and skip this one only if it is runtimeConfigValidator.ValidateConfigProperties(); if (runtimeConfig.IsDevelopmentMode()) { // Running only in developer mode to ensure fast and smooth startup in production. + // TODO: Add this check at the end of generating the new entities and skip this one only if it is runtimeConfigValidator.ValidatePermissionsInConfig(runtimeConfig); } From e5ae16a2d68a59d562b83ac60a8b62c4d782c99a Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Thu, 12 Feb 2026 14:36:42 -0800 Subject: [PATCH 12/14] Add new testing --- .../Configuration/ConfigurationTests.cs | 126 ++++++++---------- 1 file changed, 59 insertions(+), 67 deletions(-) diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index bc41f231e1..39e31d22cc 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; +using System.ComponentModel; using System.IdentityModel.Tokens.Jwt; using System.IO; using System.IO.Abstractions; @@ -5232,10 +5233,51 @@ public async Task TestGraphQLIntrospectionQueriesAreNotImpactedByDepthLimit() } [TestCategory(TestCategory.MSSQL)] - [TestMethod] - public async Task TestAutoentitiesAreGeneratedIntoEntities() + [DataTestMethod] + [DataRow(true, DisplayName = "Test Autoentities with additional entities")] + [DataRow(false, DisplayName = "Test Autoentities without additional entities")] + public async Task TestAutoentitiesAreGeneratedIntoEntities(bool useEntities) { // Arrange + EntityRelationship bookRelationship = new(Cardinality: Cardinality.One, + TargetEntity: "BookPublisher", + SourceFields: new string[] { }, + TargetFields: new string[] { }, + LinkingObject: null, + LinkingSourceFields: null, + LinkingTargetFields: null); + + Entity bookEntity = new(Source: new("books", EntitySourceType.Table, null, null), + Fields: null, + Rest: null, + GraphQL: new(Singular: "book", Plural: "books"), + Permissions: new[] { GetMinimalPermissionConfig(AuthorizationResolver.ROLE_ANONYMOUS) }, + Relationships: new Dictionary() { { "publishers", bookRelationship } }, + Mappings: null); + + EntityRelationship publisherRelationship = new(Cardinality: Cardinality.Many, + TargetEntity: "Book", + SourceFields: new string[] { }, + TargetFields: new string[] { }, + LinkingObject: null, + LinkingSourceFields: null, + LinkingTargetFields: null); + + Entity publisherEntity = new( + Source: new("publishers", EntitySourceType.Table, null, null), + Fields: null, + Rest: null, + GraphQL: new(Singular: "bookpublisher", Plural: "bookpublishers"), + Permissions: new[] { GetMinimalPermissionConfig(AuthorizationResolver.ROLE_ANONYMOUS) }, + Relationships: new Dictionary() { { "books", publisherRelationship } }, + Mappings: null); + + Dictionary entityMap = new() + { + { "Book", bookEntity }, + { "BookPublisher", publisherEntity } + }; + Dictionary autoentityMap = new() { { @@ -5243,10 +5285,10 @@ public async Task TestAutoentitiesAreGeneratedIntoEntities() Patterns: new AutoentityPatterns( Include: new[] { "%publishers%" }, Exclude: null, - Name: null // Let DAB decide entity naming + Name: null ), Template: new AutoentityTemplate( - Rest: new EntityRestOptions(Enabled: true), // Enable REST as requested + Rest: new EntityRestOptions(Enabled: true), GraphQL: new EntityGraphQLOptions( Singular: string.Empty, Plural: string.Empty, @@ -5280,14 +5322,13 @@ public async Task TestAutoentitiesAreGeneratedIntoEntities() ) ) ), - Entities: new(new Dictionary()), // Start with empty entities + Entities: new (useEntities ? entityMap : new Dictionary()), Autoentities: new RuntimeAutoentities(autoentityMap) ); - const string CUSTOM_CONFIG = "autoentities-test-config.json"; - File.WriteAllText(CUSTOM_CONFIG, configuration.ToJson()); + File.WriteAllText(CUSTOM_CONFIG_FILENAME, configuration.ToJson()); - string[] args = new[] { $"--ConfigFileName={CUSTOM_CONFIG}" }; + string[] args = new[] { $"--ConfigFileName={CUSTOM_CONFIG_FILENAME}" }; using (TestServer server = new(Program.CreateWebHostBuilder(args))) using (HttpClient client = server.CreateClient()) @@ -5314,71 +5355,22 @@ public async Task TestAutoentitiesAreGeneratedIntoEntities() HttpResponseMessage graphqlResponse = await client.SendAsync(graphqlRequest); // Assert + string expectedResponseFragment = @"{""id"":1156,""name"":""The First Publisher""}"; + // Verify REST response - Assert.AreEqual(HttpStatusCode.OK, restResponse.StatusCode, - "REST request to auto-generated entity should succeed"); + Assert.AreEqual(HttpStatusCode.OK, restResponse.StatusCode, "REST request to auto-generated entity should succeed"); - // TODO: check this thing string restResponseBody = await restResponse.Content.ReadAsStringAsync(); - Assert.IsFalse(string.IsNullOrEmpty(restResponseBody), - "REST response should contain data"); - - // Parse and validate REST response structure - JsonElement restJsonResponse = JsonSerializer.Deserialize(restResponseBody); - Assert.IsTrue(restJsonResponse.TryGetProperty("value", out JsonElement restDataArray), - "REST response should contain 'value' property with data array"); - Assert.AreEqual(JsonValueKind.Array, restDataArray.ValueKind, - "REST response data should be an array"); - - // TODO: Add specific field validation here based on your publishers table schema - // I think it can be done by sending a request to the database and get compare the values we get with this response - // Example: - // if (restDataArray.GetArrayLength() > 0) - // { - // JsonElement firstPublisher = restDataArray[0]; - // Assert.IsTrue(firstPublisher.TryGetProperty("id", out _), "Publisher should have id field"); - // Assert.IsTrue(firstPublisher.TryGetProperty("name", out _), "Publisher should have name field"); - // - // // Add more specific field and value assertions here - // } - + Assert.IsTrue(!string.IsNullOrEmpty(restResponseBody), "REST response should contain data"); + Assert.IsTrue(restResponseBody.Contains(expectedResponseFragment)); + // Verify GraphQL response - Assert.AreEqual(HttpStatusCode.OK, graphqlResponse.StatusCode, - "GraphQL request to auto-generated entity should succeed"); + Assert.AreEqual(HttpStatusCode.OK, graphqlResponse.StatusCode, "GraphQL request to auto-generated entity should succeed"); - // TODO: Check this thing, I think we can just check if it is null without having to do too much stuff with readasstringasync string graphqlResponseBody = await graphqlResponse.Content.ReadAsStringAsync(); - Assert.IsFalse(string.IsNullOrEmpty(graphqlResponseBody), - "GraphQL response should contain data"); - - // Parse and validate GraphQL response structure - JsonElement graphqlJsonResponse = JsonSerializer.Deserialize(graphqlResponseBody); - Assert.IsFalse(graphqlJsonResponse.TryGetProperty("errors", out _), - "GraphQL response should not contain errors"); - Assert.IsTrue(graphqlJsonResponse.TryGetProperty("data", out JsonElement graphqlData), - "GraphQL response should contain data"); - Assert.IsTrue(graphqlData.TryGetProperty("publishers", out JsonElement publishersData), - "GraphQL data should contain publishers"); - Assert.IsTrue(publishersData.TryGetProperty("items", out JsonElement publishersItems), - "GraphQL publishers should contain items array"); - Assert.AreEqual(JsonValueKind.Array, publishersItems.ValueKind, - "GraphQL publishers items should be an array"); - - // TODO: Add specific GraphQL field validation here based on your publishers table schema - // Example: - // if (publishersItems.GetArrayLength() > 0) - // { - // JsonElement firstPublisher = publishersItems[0]; - // Assert.IsTrue(firstPublisher.TryGetProperty("id", out JsonElement idValue), "Publisher should have id field"); - // Assert.IsTrue(firstPublisher.TryGetProperty("name", out JsonElement nameValue), "Publisher should have name field"); - // - // // Add specific value assertions here: - // // Assert.AreEqual("Expected Publisher Name", nameValue.GetString()); - // // Assert.AreEqual(123, idValue.GetInt32()); - // } - - // Success! The autoentity was successfully converted to a working entity - // that responds to both REST and GraphQL requests + Assert.IsTrue(!string.IsNullOrEmpty(graphqlResponseBody), "GraphQL response should contain data"); + Assert.IsFalse(graphqlResponseBody.Contains("errors"), "GraphQL response should not contain errors"); + Assert.IsTrue(graphqlResponseBody.Contains(expectedResponseFragment)); } } From 2f256e43c192f248339f420baf4b7c64dd8b1911 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Thu, 12 Feb 2026 14:55:07 -0800 Subject: [PATCH 13/14] Add check so that either entities or autoentities is required --- src/Config/ObjectModel/RuntimeConfig.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Config/ObjectModel/RuntimeConfig.cs b/src/Config/ObjectModel/RuntimeConfig.cs index 55de26c99d..4833ce5d07 100644 --- a/src/Config/ObjectModel/RuntimeConfig.cs +++ b/src/Config/ObjectModel/RuntimeConfig.cs @@ -292,7 +292,7 @@ public RuntimeConfig( }; _entityNameToDataSourceName = new Dictionary(); - if (Entities is null && Autoentities is null) + if ((Entities is null || Entities.Entities.Count == 0) && Autoentities is null) { throw new DataApiBuilderException( message: "Configuration file should contain either at least the Entities or Autoentities property", From 6fca50d90d29584fc164aecb2c3a408d0854ef44 Mon Sep 17 00:00:00 2001 From: Ruben Cerna Date: Thu, 12 Feb 2026 17:21:42 -0800 Subject: [PATCH 14/14] Fix grammar errors --- src/Core/Configurations/RuntimeConfigValidator.cs | 2 +- .../MetadataProviders/MsSqlMetadataProvider.cs | 1 - .../Configuration/ConfigurationTests.cs | 15 +++++++-------- 3 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Core/Configurations/RuntimeConfigValidator.cs b/src/Core/Configurations/RuntimeConfigValidator.cs index 009339bb5f..208c6fb331 100644 --- a/src/Core/Configurations/RuntimeConfigValidator.cs +++ b/src/Core/Configurations/RuntimeConfigValidator.cs @@ -602,7 +602,7 @@ public void ValidateEntityConfiguration(RuntimeConfig runtimeConfig) // Stores the unique rest paths configured for different entities present in the config. HashSet restPathsForEntities = new(); - foreach ((string entityName, Entity entity) in runtimeConfig.Entities ?? Enumerable.Empty>()) + foreach ((string entityName, Entity entity) in runtimeConfig.Entities) { if (runtimeConfig.IsRestEnabled && entity.Rest is not null && entity.Rest.Enabled) { diff --git a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs index 801d7bc493..330112ac6e 100644 --- a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs +++ b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs @@ -6,7 +6,6 @@ using System.Net; using System.Text.Json; using System.Text.Json.Nodes; -using Azure.DataApiBuilder.Auth; using Azure.DataApiBuilder.Config.DatabasePrimitives; using Azure.DataApiBuilder.Config.ObjectModel; using Azure.DataApiBuilder.Core.Configurations; diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs index 39e31d22cc..2156413375 100644 --- a/src/Service.Tests/Configuration/ConfigurationTests.cs +++ b/src/Service.Tests/Configuration/ConfigurationTests.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; -using System.ComponentModel; using System.IdentityModel.Tokens.Jwt; using System.IO; using System.IO.Abstractions; @@ -5290,8 +5289,8 @@ public async Task TestAutoentitiesAreGeneratedIntoEntities(bool useEntities) Template: new AutoentityTemplate( Rest: new EntityRestOptions(Enabled: true), GraphQL: new EntityGraphQLOptions( - Singular: string.Empty, - Plural: string.Empty, + Singular: string.Empty, + Plural: string.Empty, Enabled: true ), Health: null, @@ -5315,14 +5314,14 @@ public async Task TestAutoentitiesAreGeneratedIntoEntities(bool useEntities) GraphQL: new(Enabled: true), Mcp: new(Enabled: false), Host: new( - Cors: null, + Cors: null, Authentication: new Config.ObjectModel.AuthenticationOptions( - Provider: nameof(EasyAuthType.StaticWebApps), + Provider: nameof(EasyAuthType.StaticWebApps), Jwt: null ) ) ), - Entities: new (useEntities ? entityMap : new Dictionary()), + Entities: new(useEntities ? entityMap : new Dictionary()), Autoentities: new RuntimeAutoentities(autoentityMap) ); @@ -5363,8 +5362,8 @@ public async Task TestAutoentitiesAreGeneratedIntoEntities(bool useEntities) string restResponseBody = await restResponse.Content.ReadAsStringAsync(); Assert.IsTrue(!string.IsNullOrEmpty(restResponseBody), "REST response should contain data"); Assert.IsTrue(restResponseBody.Contains(expectedResponseFragment)); - - // Verify GraphQL response + + // Verify GraphQL response Assert.AreEqual(HttpStatusCode.OK, graphqlResponse.StatusCode, "GraphQL request to auto-generated entity should succeed"); string graphqlResponseBody = await graphqlResponse.Content.ReadAsStringAsync();