diff --git a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs
index cdb54a2ac2..6438a7a663 100644
--- a/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs
+++ b/src/Core/Services/MetadataProviders/MsSqlMetadataProvider.cs
@@ -333,6 +333,9 @@ protected override async Task GenerateAutoentitiesIntoEntities(IReadOnlyDictiona
continue;
}
+ // Sanitize the entity name by ensuring all whitespace characters are removed.
+ entityName = SanitizeGeneratedEntityName(entityName);
+
// 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(
@@ -386,6 +389,12 @@ protected override async Task GenerateAutoentitiesIntoEntities(IReadOnlyDictiona
_runtimeConfigProvider.AddMergedEntitiesToConfig(entities);
}
+ ///
+ /// Queries the database for autoentities based on the provided autoentity definition.
+ ///
+ /// The name of the autoentity definition.
+ /// The autoentity definition containing patterns for inclusion, exclusion, and name.
+ /// A JsonArray containing the queried autoentities, or an empty array if none are found.
public async Task QueryAutoentitiesAsync(string autoentityName, Autoentity autoentity)
{
string include = string.Join(",", autoentity.Patterns.Include);
diff --git a/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs b/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs
index d93b4edbcf..9db0b48827 100644
--- a/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs
+++ b/src/Core/Services/MetadataProviders/SqlMetadataProvider.cs
@@ -733,6 +733,31 @@ private void RemoveGeneratedAutoentities()
_runtimeConfigProvider.RemoveGeneratedAutoentitiesFromConfig();
}
+ ///
+ /// Sanitizes the generated entity name by removing whitespace and capitalizing the next character after whitespace.
+ ///
+ /// The entity name to be sanitized.
+ /// The sanitized entity name.
+ protected static string SanitizeGeneratedEntityName(string name)
+ {
+ StringBuilder sanitizedName = new(name.Length);
+ bool capitalizeNext = false;
+
+ foreach (char character in name)
+ {
+ if (char.IsWhiteSpace(character))
+ {
+ capitalizeNext = true;
+ continue;
+ }
+
+ sanitizedName.Append(capitalizeNext ? char.ToUpperInvariant(character) : character);
+ capitalizeNext = false;
+ }
+
+ return sanitizedName.ToString();
+ }
+
protected void PopulateDatabaseObjectForEntity(
Entity entity,
string entityName,
diff --git a/src/Service.Tests/Configuration/ConfigurationTests.cs b/src/Service.Tests/Configuration/ConfigurationTests.cs
index 203de98aef..15342308fd 100644
--- a/src/Service.Tests/Configuration/ConfigurationTests.cs
+++ b/src/Service.Tests/Configuration/ConfigurationTests.cs
@@ -5759,16 +5759,17 @@ public async Task TestAutoentitiesWithSameObjectDifferentSchemas()
}
///
- /// Ensures that autoentities are properly generated into in-memory entities when entities have non-default schemas.
+ /// Ensures that autoentities are properly generated into in-memory entities when entities have unusual elements such as non-default schemas or spaces in their names.
///
/// The pattern to include for autoentities
- /// Boolean that indicates if the pattern is for the foo schema
+ /// Integer that indicates which input pattern is being used
///
[TestCategory(TestCategory.MSSQL)]
[DataTestMethod]
- [DataRow("foo.%", true, DisplayName = "Test Autoentities with foo schema")]
- [DataRow("bar.%", false, DisplayName = "Test Autoentities with bar schema")]
- public async Task TestAutoentitiesGeneratedWithDifferentSchemas(string includePattern, bool isPatternFoo)
+ [DataRow("foo.%", 0, DisplayName = "Test Autoentities with foo schema")]
+ [DataRow("bar.%", 1, DisplayName = "Test Autoentities with bar schema")]
+ [DataRow("dbo.Order Items", 2, DisplayName = "Test Autoentities with object with spaces")]
+ public async Task TestAutoentitiesGeneratedWithUnusualElements(string includePattern, int patternType)
{
// Arrange
Dictionary autoentityMap = new()
@@ -5826,11 +5827,33 @@ public async Task TestAutoentitiesGeneratedWithDifferentSchemas(string includePa
using (HttpClient client = server.CreateClient())
{
// Act
- string path = isPatternFoo ? "foo_magazines" : "bar_magazines";
+ string path;
+ string item;
+ string expectedResponseFragment;
+ switch (patternType)
+ {
+ case 0:
+ path = "foo_magazines";
+ item = "title";
+ expectedResponseFragment = @"""title"":""Vogue""";
+ break;
+ case 1:
+ path = "bar_magazines";
+ item = "comic_name";
+ expectedResponseFragment = @"""comic_name"":""NotVogue""";
+ break;
+ case 2:
+ path = "dbo_OrderItems";
+ item = "productname";
+ expectedResponseFragment = @"""productname"":""Sample Product""";
+ break;
+ default:
+ throw new ArgumentException("Invalid pattern type");
+ }
+
using HttpRequestMessage restRequest = new(HttpMethod.Get, $"/api/{path}");
using HttpResponseMessage restResponse = await client.SendAsync(restRequest);
- string item = isPatternFoo ? "title" : "comic_name";
string graphqlQuery = $@"
{{
{path} {{
@@ -5848,8 +5871,6 @@ public async Task TestAutoentitiesGeneratedWithDifferentSchemas(string includePa
HttpResponseMessage graphqlResponse = await client.SendAsync(graphqlRequest);
// Assert
- string expectedResponseFragment = isPatternFoo ? @"""title"":""Vogue""" : @"""comic_name"":""NotVogue""";
-
// Verify REST response
Assert.AreEqual(HttpStatusCode.OK, restResponse.StatusCode, "REST request to auto-generated entity should succeed");
diff --git a/src/Service.Tests/DatabaseSchema-MsSql.sql b/src/Service.Tests/DatabaseSchema-MsSql.sql
index 4e87394aee..472cfac133 100644
--- a/src/Service.Tests/DatabaseSchema-MsSql.sql
+++ b/src/Service.Tests/DatabaseSchema-MsSql.sql
@@ -63,6 +63,7 @@ DROP TABLE IF EXISTS date_only_table;
DROP TABLE IF EXISTS users;
DROP TABLE IF EXISTS user_profiles;
DROP TABLE IF EXISTS default_books;
+DROP TABLE IF EXISTS [Order Items];
DROP SCHEMA IF EXISTS [foo];
DROP SCHEMA IF EXISTS [bar];
COMMIT;
@@ -321,21 +322,23 @@ CREATE TABLE mappedbookmarks
bkname nvarchar(50) NOT NULL
)
-create Table fte_data(
-id int IDENTITY(5001,1),
-u_id int DEFAULT 2,
-name varchar(50),
-position varchar(20),
-salary int default 20,
-PRIMARY KEY(id, u_id)
+create Table fte_data
+(
+ id int IDENTITY(5001,1),
+ u_id int DEFAULT 2,
+ name varchar(50),
+ position varchar(20),
+ salary int default 20,
+ PRIMARY KEY(id, u_id)
);
-create Table intern_data(
-id int,
-months int default 2 NOT NULL,
-name varchar(50),
-salary int default 15,
-PRIMARY KEY(id, months)
+create Table intern_data
+(
+ id int,
+ months int default 2 NOT NULL,
+ name varchar(50),
+ salary int default 15,
+ PRIMARY KEY(id, months)
);
create table books_sold
@@ -394,6 +397,11 @@ CREATE TABLE default_books(
title NVARCHAR(100)
);
+CREATE TABLE [Order Items](
+ id INT PRIMARY KEY,
+ productname VARCHAR(100)
+);
+
ALTER TABLE books
ADD CONSTRAINT book_publisher_fk
FOREIGN KEY (publisher_id)
@@ -826,3 +834,6 @@ INSERT INTO date_only_table( event_date, event_time, event_timestamp)
VALUES ('2023-01-01', '08:30:00', '2023-01-01 08:30:00'),
('2023-02-15', '12:45:00', '2023-02-15 12:45:00'),
('2023-03-30', '17:15:00', '2023-03-30 17:15:00');
+
+INSERT INTO [Order Items](id, productname)
+VALUES (1, 'Sample Product');