Create Entities in-memory from Autoentities#3129
Create Entities in-memory from Autoentities#3129RubenCerna2079 wants to merge 15 commits intomainfrom
Conversation
1972a8f to
2f256e4
Compare
|
/azp run |
|
Azure Pipelines successfully started running 6 pipeline(s). |
There was a problem hiding this comment.
Pull request overview
This PR enables auto-generating entity definitions from autoentities at runtime (in-memory) so the generated entities become usable by the REST/GraphQL runtime without being serialized into the entities section of the config.
Changes:
- Implemented MSSQL autoentity generation to materialize matching tables into
RuntimeConfig.Entitiesduring metadata initialization. - Updated
RuntimeConfig/validation to allowentitiesto be omitted whenautoentitiesis present, and added APIs to mutate the in-memory runtime config. - Added an MSSQL test covering autoentities generation with/without pre-existing entities.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| src/Service/Startup.cs | Adds TODOs related to validation placement with autoentity generation. |
| src/Service.Tests/Configuration/ConfigurationTests.cs | Adds test coverage for autoentities generating usable REST/GraphQL endpoints. |
| src/Core/Services/MetadataProviders/SqlMetadataProvider.cs | Exposes _entities to derived classes and triggers autoentity generation during MSSQL initialization. |
| src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs | Implements GenerateAutoentitiesIntoEntities() and query execution for autoentities. |
| src/Core/Configurations/RuntimeConfigValidator.cs | Allows entity validation loop to safely handle missing Entities. |
| src/Core/Configurations/RuntimeConfigProvider.cs | Adds method to replace runtime config entities in-memory after generation. |
| src/Config/RuntimeConfigLoader.cs | Adds a setter-style method to update the active RuntimeConfig instance. |
| src/Config/ObjectModel/RuntimeConfig.cs | Allows missing entities when autoentities exists; adds entity->datasource mapping helper. |
| public bool TryAddEntityNameToDataSourceName(string entityName) | ||
| { | ||
| return _entityNameToDataSourceName.TryAdd(entityName, this.DefaultDataSourceName); |
There was a problem hiding this comment.
This method always maps newly added entities to DefaultDataSourceName. That will be incorrect when autoentities are generated while initializing a non-default datasource (multi-database scenario). Consider changing the API to accept a dataSourceName (and use _dataSourceName from the metadata provider) so the entity->datasource mapping stays correct.
| public bool TryAddEntityNameToDataSourceName(string entityName) | |
| { | |
| return _entityNameToDataSourceName.TryAdd(entityName, this.DefaultDataSourceName); | |
| public bool TryAddEntityNameToDataSourceName(string entityName, string? dataSourceName = null) | |
| { | |
| string effectiveDataSourceName = string.IsNullOrEmpty(dataSourceName) ? this.DefaultDataSourceName : dataSourceName; | |
| return _entityNameToDataSourceName.TryAdd(entityName, effectiveDataSourceName); |
| RuntimeConfig newRuntimeConfig = _configLoader.RuntimeConfig! with | ||
| { | ||
| Entities = new(entities) | ||
| }; |
There was a problem hiding this comment.
AddNewEntitiesToConfig replaces the entire RuntimeConfig.Entities collection with the provided dictionary. In multi-datasource scenarios (or any scenario where entities is a subset), this can unintentionally drop entities and desync internal lookup dictionaries. Prefer merging with the existing entity set (and updating the entity->datasource map consistently) rather than overwriting.
| RuntimeConfig newRuntimeConfig = _configLoader.RuntimeConfig! with | |
| { | |
| Entities = new(entities) | |
| }; | |
| // Merge incoming entities into the existing entity set instead of overwriting it. | |
| RuntimeConfig currentRuntimeConfig = _configLoader.RuntimeConfig!; | |
| Dictionary<string, Entity> mergedEntities = new(currentRuntimeConfig.Entities); | |
| foreach ((string entityName, Entity entityDefinition) in entities) | |
| { | |
| // Add new entities or update existing ones with the provided definitions. | |
| mergedEntities[entityName] = entityDefinition; | |
| } | |
| RuntimeConfig newRuntimeConfig = currentRuntimeConfig with | |
| { | |
| Entities = mergedEntities | |
| }; |
| Dictionary<string, Entity> entities = (Dictionary<string, Entity>)_entities; | ||
| if (runtimeConfig.Autoentities is null) | ||
| { | ||
| return; | ||
| } | ||
|
|
There was a problem hiding this comment.
entities is taken from _entities, which is a per-datasource filtered dictionary (see SqlMetadataProvider ctor). Passing that dictionary into AddNewEntitiesToConfig risks replacing RuntimeConfig.Entities with only the entities for this datasource, dropping entities for other datasources and leaving _entityNameToDataSourceName inconsistent. Use the full runtimeConfig.Entities.Entities as the base and merge in generated entities, or make AddNewEntitiesToConfig merge rather than replace.
| Dictionary<string, Entity> entities = (Dictionary<string, Entity>)_entities; | |
| if (runtimeConfig.Autoentities is null) | |
| { | |
| return; | |
| } | |
| if (runtimeConfig.Autoentities is null) | |
| { | |
| return; | |
| } | |
| // Start from the full set of entities defined in the runtime configuration, | |
| // not the per-datasource filtered _entities dictionary, so that when we | |
| // persist new auto-generated entities we don't drop entities for other datasources. | |
| Dictionary<string, Entity> entities = new(runtimeConfig.Entities.Entities); |
| string entityName = resultObject["entity_name"]!.ToString(); | ||
| string schemaName = resultObject["schema"]!.ToString(); | ||
| string objectName = resultObject["object"]!.ToString(); | ||
|
|
||
| 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), |
There was a problem hiding this comment.
schemaName is read from the autoentities query but not used. If a matched table is in a non-default schema, setting EntitySource.Object to just objectName will make DAB resolve it against the default schema (dbo) and point to the wrong table. Consider using a schema-qualified source (e.g., schemaName.objectName) when schemaName is not empty/default.
| using (HttpClient client = server.CreateClient()) | ||
| { | ||
| // Act | ||
| HttpRequestMessage restRequest = new(HttpMethod.Get, "/api/publishers"); |
There was a problem hiding this comment.
Disposable 'HttpRequestMessage' is created but not disposed.
| HttpRequestMessage graphqlRequest = new(HttpMethod.Post, "/graphql") | ||
| { | ||
| Content = JsonContent.Create(graphqlPayload) | ||
| }; |
There was a problem hiding this comment.
Disposable 'HttpRequestMessage' is created but not disposed.
|
/azp run |
|
Azure Pipelines successfully started running 6 pipeline(s). |
| { | ||
| 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", |
There was a problem hiding this comment.
| message: "Configuration file should contain either at least the Entities or Autoentities property", | |
| message: "Configuration file should contain either at least the entities or autoentities property", |
| } | ||
| else | ||
| { | ||
| _runtimeConfigProvider.AddNewEntitiesToConfig(entities); |
There was a problem hiding this comment.
Dont we have access to the RuntimeConfigLoader here?
| return runtimeConfig; | ||
| } | ||
|
|
||
| public void AddNewEntitiesToConfig(Dictionary<string, Entity> entities) |
There was a problem hiding this comment.
Change the function name to "AddMergedEntitiesToConfig" since the entities passed in here, are all the entities - original + entities found from autoentities.
|
|
||
| string[] args = new[] { $"--ConfigFileName={CUSTOM_CONFIG_FILENAME}" }; | ||
|
|
||
| using (TestServer server = new(Program.CreateWebHostBuilder(args))) |
There was a problem hiding this comment.
Can you also retrieve the serviceProvider -> RuntimeConfigProvider and the RuntimeConfig to check the entities member has 3 entities with useentity and 1 when not using previous entities?
|
/azp run |
|
Azure Pipelines successfully started running 6 pipeline(s). |
Why make this change?
We need to generate all the entities from the autoentities properties. In order to do this we need to use the query that was previously created and, add the newly generated entities into the runtime so the user can use them.
What is this change?
MsSqlMetadataProvider.cs: Finish creating theGenerateAutoentitiesIntoEntitiesfunction so that it uses the query to receive all of the tables and turn them into entities inside the runtimeConfig.RuntimeConfig.cs: Create new function that adds the new entities to the runtimeConfig. And also change the runtimeConfig to allow for theentitiesproperty to be missing if the user decides to use theautoentitiesproperty.How was this tested?