Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
a9a4ea0
Changes for autoentities
RubenCerna2079 Jan 22, 2026
4acee5c
Add query and its execution
RubenCerna2079 Jan 26, 2026
67e10d7
Add testing
RubenCerna2079 Jan 27, 2026
5453cbc
Changes based on comments
RubenCerna2079 Jan 29, 2026
8fbea75
Changes based on comments
RubenCerna2079 Jan 29, 2026
cf4debf
Added changes based on comments
RubenCerna2079 Jan 30, 2026
88b8c66
Comment out failing section
RubenCerna2079 Jan 30, 2026
612d4bd
Fix test failures
RubenCerna2079 Jan 30, 2026
a396fab
Fix formatting issues
RubenCerna2079 Jan 30, 2026
c8ed0d5
Generate in-memory entities
RubenCerna2079 Feb 5, 2026
947fdec
Changes to generate autoentities as entities
RubenCerna2079 Feb 11, 2026
e5ae16a
Add new testing
RubenCerna2079 Feb 12, 2026
2f256e4
Add check so that either entities or autoentities is required
RubenCerna2079 Feb 12, 2026
6fca50d
Fix grammar errors
RubenCerna2079 Feb 13, 2026
8b88815
Merge branch 'main' into dev/rubencerna/create-inmemory-entities-from…
RubenCerna2079 Feb 13, 2026
ba070db
Changes to fix tests
RubenCerna2079 Feb 13, 2026
5bd10a9
Changes based on comments
RubenCerna2079 Feb 17, 2026
33f33c3
Merge branch 'main' into dev/rubencerna/create-inmemory-entities-from…
RubenCerna2079 Feb 17, 2026
83d706f
Fix bug
RubenCerna2079 Feb 18, 2026
9ab35a6
Merge branch 'main' into dev/rubencerna/create-inmemory-entities-from…
RubenCerna2079 Feb 18, 2026
4a743c7
Changes to ensure autoentities work with multiple data sources
RubenCerna2079 Feb 20, 2026
7570931
Merge branch 'main' into dev/rubencerna/create-inmemory-entities-from…
RubenCerna2079 Feb 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/Config/Converters/RuntimeAutoentitiesConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ class RuntimeAutoentitiesConverter : JsonConverter<RuntimeAutoentities>
public override void Write(Utf8JsonWriter writer, RuntimeAutoentities value, JsonSerializerOptions options)
{
writer.WriteStartObject();
foreach ((string key, Autoentity autoEntity) in value.AutoEntities)
foreach ((string key, Autoentity autoEntity) in value.Autoentities)
{
writer.WritePropertyName(key);
JsonSerializer.Serialize(writer, autoEntity, options);
Expand Down
21 changes: 16 additions & 5 deletions src/Config/ObjectModel/RuntimeAutoentities.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

using System.Collections;
using System.Text.Json.Serialization;
using Azure.DataApiBuilder.Config.Converters;

Expand All @@ -10,19 +11,29 @@ namespace Azure.DataApiBuilder.Config.ObjectModel;
/// Represents a collection of <see cref="Autoentity"/> available from the RuntimeConfig.
/// </summary>
[JsonConverter(typeof(RuntimeAutoentitiesConverter))]
public record RuntimeAutoentities
public record RuntimeAutoentities : IEnumerable<KeyValuePair<string, Autoentity>>
{
/// <summary>
/// The collection of <see cref="Autoentity"/> available from the RuntimeConfig.
/// </summary>
public IReadOnlyDictionary<string, Autoentity> AutoEntities { get; init; }
public IReadOnlyDictionary<string, Autoentity> Autoentities { get; init; }

/// <summary>
/// Creates a new instance of the <see cref="RuntimeAutoentities"/> class using a collection of entities.
/// </summary>
/// <param name="autoEntities">The collection of auto-entities to map to RuntimeAutoentities.</param>
public RuntimeAutoentities(IReadOnlyDictionary<string, Autoentity> autoEntities)
/// <param name="autoentities">The collection of auto-entities to map to RuntimeAutoentities.</param>
public RuntimeAutoentities(IReadOnlyDictionary<string, Autoentity> autoentities)
{
AutoEntities = autoEntities;
Autoentities = autoentities;
}

public IEnumerator<KeyValuePair<string, Autoentity>> GetEnumerator()
{
return Autoentities.GetEnumerator();
}

IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
}
79 changes: 68 additions & 11 deletions src/Config/ObjectModel/RuntimeConfig.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public record RuntimeConfig
[JsonPropertyName("azure-key-vault")]
public AzureKeyVaultOptions? AzureKeyVault { get; init; }

public RuntimeAutoentities? Autoentities { get; init; }
public RuntimeAutoentities Autoentities { get; init; }

public virtual RuntimeEntities Entities { get; init; }

Expand Down Expand Up @@ -216,6 +216,8 @@ Runtime.GraphQL.FeatureFlags is not null &&

private Dictionary<string, string> _entityNameToDataSourceName = new();

private Dictionary<string, string> _autoentityNameToDataSourceName = new();

private Dictionary<string, string> _entityPathNameToEntityName = new();

/// <summary>
Expand Down Expand Up @@ -245,6 +247,21 @@ public bool TryGetEntityNameFromPath(string entityPathName, [NotNullWhen(true)]
return _entityPathNameToEntityName.TryGetValue(entityPathName, out entityName);
}

public bool TryAddEntityNameToDataSourceName(string entityName)
{
return _entityNameToDataSourceName.TryAdd(entityName, this.DefaultDataSourceName);
}

public bool TryAddEntityNameToDataSourceName(string entityName, string autoEntityDefinition)
Copy link
Collaborator

@Aniruddh25 Aniruddh25 Feb 20, 2026

Choose a reason for hiding this comment

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

nit: rename the function to inform we are adding entity name from AutoentityDefinition

{
if (_autoentityNameToDataSourceName.TryGetValue(autoEntityDefinition, out string? dataSourceName))
{
return _entityNameToDataSourceName.TryAdd(entityName, dataSourceName);
}

return false;
}

/// <summary>
/// Constructor for runtimeConfig.
/// To be used when setting up from cli json scenario.
Expand All @@ -268,8 +285,8 @@ public RuntimeConfig(
this.DataSource = DataSource;
this.Runtime = Runtime;
this.AzureKeyVault = AzureKeyVault;
this.Entities = Entities;
this.Autoentities = Autoentities;
this.Entities = Entities ?? new RuntimeEntities(new Dictionary<string, Entity>());
this.Autoentities = Autoentities ?? new RuntimeAutoentities(new Dictionary<string, Autoentity>());
this.DefaultDataSourceName = Guid.NewGuid().ToString();

if (this.DataSource is null)
Expand All @@ -287,25 +304,38 @@ public RuntimeConfig(
};

_entityNameToDataSourceName = new Dictionary<string, string>();
if (Entities is null)
if (Entities is null && this.Entities.Entities.Count == 0 &&
Copy link
Collaborator

Choose a reason for hiding this comment

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

This condition will throw NullException

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I am not following why this would throw a NullException

Copy link
Collaborator

Choose a reason for hiding this comment

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

please rename Entities to entities since its a function argument

Autoentities is null && this.Autoentities.Autoentities.Count == 0)
{
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<string, Entity> entity in Entities)
if (Entities is not null)
{
foreach (KeyValuePair<string, Entity> entity in Entities)
{
_entityNameToDataSourceName.TryAdd(entity.Key, this.DefaultDataSourceName);
}
}

if (Autoentities is not null)
{
_entityNameToDataSourceName.TryAdd(entity.Key, this.DefaultDataSourceName);
foreach (KeyValuePair<string, Autoentity> autoentity in Autoentities)
{
_autoentityNameToDataSourceName.TryAdd(autoentity.Key, this.DefaultDataSourceName);
}
}

// Process data source and entities information for each database in multiple database scenario.
this.DataSourceFiles = DataSourceFiles;

if (DataSourceFiles is not null && DataSourceFiles.SourceFiles is not null)
{
IEnumerable<KeyValuePair<string, Entity>> allEntities = Entities.AsEnumerable();
IEnumerable<KeyValuePair<string, Entity>>? allEntities = Entities?.AsEnumerable();
IEnumerable<KeyValuePair<string, Autoentity>>? allAutoentities = Autoentities?.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.
Expand All @@ -322,7 +352,9 @@ 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());
_autoentityNameToDataSourceName = _autoentityNameToDataSourceName.Concat(config._autoentityNameToDataSourceName).ToDictionary(kvp => kvp.Key, kvp => kvp.Value);
allEntities = allEntities?.Concat(config.Entities.AsEnumerable());
allAutoentities = allAutoentities?.Concat(config.Autoentities.AsEnumerable());
}
catch (Exception e)
{
Expand All @@ -336,7 +368,8 @@ 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<string, Entity>());
this.Autoentities = new RuntimeAutoentities(allAutoentities != null ? allAutoentities.ToDictionary(x => x.Key, x => x.Value) : new Dictionary<string, Autoentity>());
}

SetupDataSourcesUsed();
Expand All @@ -351,17 +384,19 @@ public RuntimeConfig(
/// <param name="DataSource">Default datasource.</param>
/// <param name="Runtime">Runtime settings.</param>
/// <param name="Entities">Entities</param>
/// <param name="Autoentities">Autoentities</param>
/// <param name="DataSourceFiles">List of datasource files for multiple db scenario.Null for single db scenario.
/// <param name="DefaultDataSourceName">DefaultDataSourceName to maintain backward compatibility.</param>
/// <param name="DataSourceNameToDataSource">Dictionary mapping datasourceName to datasource object.</param>
/// <param name="EntityNameToDataSourceName">Dictionary mapping entityName to datasourceName.</param>
/// <param name="DataSourceFiles">Datasource files which represent list of child runtimeconfigs for multi-db scenario.</param>
public RuntimeConfig(string Schema, DataSource DataSource, RuntimeOptions Runtime, RuntimeEntities Entities, string DefaultDataSourceName, Dictionary<string, DataSource> DataSourceNameToDataSource, Dictionary<string, string> EntityNameToDataSourceName, DataSourceFiles? DataSourceFiles = null, AzureKeyVaultOptions? AzureKeyVault = null)
public RuntimeConfig(string Schema, DataSource DataSource, RuntimeOptions Runtime, RuntimeEntities Entities, string DefaultDataSourceName, Dictionary<string, DataSource> DataSourceNameToDataSource, Dictionary<string, string> EntityNameToDataSourceName, DataSourceFiles? DataSourceFiles = null, AzureKeyVaultOptions? AzureKeyVault = null, RuntimeAutoentities? Autoentities = null)
{
this.Schema = Schema;
this.DataSource = DataSource;
this.Runtime = Runtime;
this.Entities = Entities;
this.Autoentities = Autoentities ?? new RuntimeAutoentities(new Dictionary<string, Autoentity>());
this.DefaultDataSourceName = DefaultDataSourceName;
_dataSourceNameToDataSource = DataSourceNameToDataSource;
_entityNameToDataSourceName = EntityNameToDataSourceName;
Expand Down Expand Up @@ -451,6 +486,17 @@ public DataSource GetDataSourceFromEntityName(string entityName)
return _dataSourceNameToDataSource[_entityNameToDataSourceName[entityName]];
}

/// <summary>
/// Gets datasourceName from AutoentityNameToDatasourceName dictionary.
/// </summary>
/// <param name="autoentityName">autoentityName</param>
/// <returns>DataSourceName</returns>
public string GetDataSourceNameFromAutoentityName(string autoentityName)
{
CheckAutoentityNamePresent(autoentityName);
Copy link
Collaborator

Choose a reason for hiding this comment

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

instead of 2 functions, why cant we use TryGet function? if false, you can throw exception.

return _autoentityNameToDataSourceName[autoentityName];
}

/// <summary>
/// Validates if datasource is present in runtimeConfig.
/// </summary>
Expand Down Expand Up @@ -588,6 +634,17 @@ private void CheckEntityNamePresent(string entityName)
}
}

private void CheckAutoentityNamePresent(string autoentityName)
{
if (!_autoentityNameToDataSourceName.ContainsKey(autoentityName))
{
throw new DataApiBuilderException(
message: $"{autoentityName} is not a valid autoentity.",
statusCode: HttpStatusCode.NotFound,
subStatusCode: DataApiBuilderException.SubStatusCodes.EntityNotFound);
}
}

private void SetupDataSourcesUsed()
{
SqlDataSourceUsed = _dataSourceNameToDataSource.Values.Any
Expand Down
5 changes: 5 additions & 0 deletions src/Config/RuntimeConfigLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -499,4 +499,9 @@ public void InsertWantedChangesInProductionMode()
RuntimeConfig = runtimeConfigCopy;
}
}

public void EditRuntimeConfig(RuntimeConfig newRuntimeConfig)
{
RuntimeConfig = newRuntimeConfig;
}
}
15 changes: 15 additions & 0 deletions src/Core/Configurations/RuntimeConfigProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -411,4 +411,19 @@ private static RuntimeConfig HandleCosmosNoSqlConfiguration(string? schema, Runt

return runtimeConfig;
}

public void AddMergedEntitiesToConfig(Dictionary<string, Entity> newEntities)
{
Dictionary<string, Entity> entities = new(_configLoader.RuntimeConfig!.Entities);
foreach((string name, Entity entity) in newEntities)
{
entities.Add(name, entity);
}

RuntimeConfig newRuntimeConfig = _configLoader.RuntimeConfig! with
{
Entities = new(entities)
};
_configLoader.EditRuntimeConfig(newRuntimeConfig);
}
}
88 changes: 86 additions & 2 deletions src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -292,9 +292,93 @@ private bool TryResolveDbType(string sqlDbTypeName, out DbType dbType)
}

/// <inheritdoc/>
protected override async Task GenerateAutoentitiesIntoEntities()
protected override async Task GenerateAutoentitiesIntoEntities(Dictionary<string, Autoentity>? autoentities)
{
await Task.CompletedTask;
RuntimeConfig runtimeConfig = _runtimeConfigProvider.GetConfig();
Dictionary<string, Entity> entities = new();
Copy link
Collaborator

Choose a reason for hiding this comment

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

define entities where its needed. Since autoentities could still be null, defining here is pre-mature if we are just going to return.

if (autoentities is null)
{
return;
}

foreach ((string autoentityName, Autoentity autoentity) in autoentities)
{
int addedEntities = 0;
JsonArray? resultArray = await QueryAutoentitiesAsync(autoentity);
Copy link
Collaborator

Choose a reason for hiding this comment

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

when querying, are we asking the right datasource to query from? How is that information being passed to QueryAutoentitiesAsync?

if (resultArray is null)
{
continue;
}

foreach (JsonObject? resultObject in resultArray)
{
if (resultObject is null)
{
throw new DataApiBuilderException(
message: $"Cannot create new entity from autoentity pattern due to an internal error.",
statusCode: HttpStatusCode.InternalServerError,
subStatusCode: DataApiBuilderException.SubStatusCodes.ErrorInInitialization);
}

// Extract the entity name, schema, and database object name from the query result.
// The SQL query returns these values with placeholders already replaced.
string? entityName = resultObject["entity_name"]?.ToString();
string? objectName = resultObject["object"]?.ToString();
string? schemaName = resultObject["schema"]?.ToString();

if (string.IsNullOrWhiteSpace(entityName) || string.IsNullOrWhiteSpace(objectName) || string.IsNullOrWhiteSpace(schemaName))
{
_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.
if (!entities.TryAdd(entityName, generatedEntity) || !runtimeConfig.TryAddEntityNameToDataSourceName(entityName, autoentityName))
{
throw new DataApiBuilderException(
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);
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why is only Rest information being logged?

}
else
{
_logger.LogInformation(message: "REST calls are disabled for the entity: {entity}", entityName);
}

addedEntities++;
}

if (addedEntities == 0)
{
_logger.LogWarning($"No new entities were generated from the autoentity {autoentityName} defined in the configuration.");
}
}

_runtimeConfigProvider.AddMergedEntitiesToConfig(entities);
}

public async Task<JsonArray?> QueryAutoentitiesAsync(Autoentity autoentity)
Expand Down
Loading
Loading