2023-09-24
Learn how to retrieve generic objects from SQL Server using table names and object IDs in ASP.NET Core.
If you’ve ever encountered a situation where you need to fetch generic objects from a SQL Server database using table names and object IDs in an ASP.NET Core application, you might have found it challenging, especially when working with Entity Framework Core.
Up until Entity Framework Core 7, there was no built-in support for using the generic SqlQueryRaw
When opting for the Raw SQL Approach, it’s crucial to be mindful of preventing SQL injection attacks, even if you don’t use user-provided values in your queries. Here’s a snippet from a controller demonstrating the approach:
inside a Controller:
/// <summary>
/// Check if the provided schema and table name is valid and not vulnerable to SQL injection attack.
/// </summary>
/// <remarks>Table name format: schema.name</remarks>
/// <param name="schemaAndTableName">schema.name</param>
/// <returns>True if the table name is valid; otherwise, false</returns>
[NonAction]
private bool IsSchemaAndTableNameValid(string schemaAndTableName)
{
// Ensure the input is not empty
if (string.IsNullOrEmpty(schemaAndTableName))
{
return false;
}
// Retrieve a list of schema-qualified table names from the database context
var tableNames = dbContext.Model.GetEntityTypes()
.Select(t => t.GetSchemaQualifiedTableName())
.Distinct();
// Check if the provided schema and table name exists in the database
return tableNames.Contains(schemaAndTableName);
}
/// <summary>
/// Retrieve JSON data from a specified table for a given object ID.
/// </summary>
/// <remarks>Table name format: schema.name</remarks>
/// <param name="schemaAndTableName">schema.name</param>
/// <param name="objectId">The object identifier</param>
/// <returns>JSON data as a string or an error message</returns>
[NonAction]
private string RetrieveJsonFromTable(string schemaAndTableName, int objectId)
{
if (!IsSchemaAndTableNameValid(schemaAndTableName))
{
return "Invalid table name: " + schemaAndTableName;
}
// Create parameters for the SQL query
SqlParameter objectIdParam = new SqlParameter("@ObjectId", objectId);
SqlParameter returnParam = new SqlParameter
{
ParameterName = "ReturnValue",
SqlDbType = SqlDbType.NVarChar,
Size = -1,
Direction = ParameterDirection.Output,
};
// Construct the SQL query to retrieve JSON data
string query = $"SELECT @ReturnValue = (SELECT * FROM {schemaAndTableName} WHERE ObjectId = @ObjectId AND Deleted = 0 FOR JSON AUTO, INCLUDE_NULL_VALUES, WITHOUT_ARRAY_WRAPPER);";
// Execute the SQL query and return the JSON result
var result = dbContext.Database.ExecuteSqlRaw(query, returnParam, objectIdParam);
return (string)returnParam.Value;
}
The Stored Procedure Approach offers a more secure and powerful way to retrieve JSON data. You can benefit from the power of the database using stored procedure. Here’s a code snippet from a controller showcasing this approach:
inside a Controller:
/// <summary>
/// Get JSON data from a specified table.
/// </summary>
/// <remarks>Table name format: schema.name</remarks>
/// <param name="schemaAndTableName">schema.name</param>
/// <returns>JSON data as a string or an error message</returns>
[NonAction]
private string GetJsonListData(string schemaAndTableName)
{
if (!IsSchemaAndTableNameValid(schemaAndTableName))
{
return "Invalid table name: " + schemaAndTableName;
}
// Execute the stored procedure and retrieve the JSON list result
var response = dbContext.spGetDataAsJson
.FromSqlInterpolated($"EXEC [dbo].[spGetDataAsJson] {schemaAndTableName}, {schemaAndTableName.Split(".")[1]}")
.AsEnumerable()
.FirstOrDefault();
return response.JsonResult;
// spGetDataAsJson is a keyless entity which has only a string properpty named JsonResult
// Equavelent in EF Core 7:
// return dbContext.Database.SqlQuery<string>("EXEC [dbo].[spGetDataAsJson] @SchemaAndTableName, @TableName, @ObjectId",
// schemaAndTableName, schemaAndTableName.Split(".")[1], objectId);
}
/// <summary>
/// Get JSON data for a specific object from a table.
/// </summary>
/// <remarks>Table name format: schema.name</remarks>
/// <param name="schemaAndTableName">schema.name</param>
/// <param name="objectId">The object identifier</param>
/// <returns>JSON data as a string or an error message</returns>
[NonAction]
private string GetJsonData(string schemaAndTableName, int objectId)
{
if (!IsSchemaAndTableNameValid(schemaAndTableName))
{
return "Invalid table name: " + schemaAndTableName;
}
// Execute the stored procedure with the object identifier and retrieve the JSON result
var response = dbContext.spGetDataAsJson
.FromSqlInterpolated($"EXEC [dbo].[spGetDataAsJson] {schemaAndTableName}, {schemaAndTableName.Split(".")[1]}, {objectId}")
.AsEnumerable()
.FirstOrDefault();
return response.JsonResult;
}
inside the MSSQL Server:
CREATE PROCEDURE [dbo].[spGetDataAsJson]
@SchemaAndTableName NVARCHAR(500) = '',
@TableName NVARCHAR(500) = '',
@ObjectId INT = NULL
AS
BEGIN
DECLARE @query NVARCHAR(1000) = '';
DECLARE @columns NVARCHAR(1000) = '';
-- Retrieve the column names for the specified table
-- while excluding unnecessary columns (e.g., 'Deleted')
SELECT @columns = STRING_AGG(ISNULL(COLUMN_NAME, ' '), ',')
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @TableName AND COLUMN_NAME NOT IN ('Deleted')
-- Construct the dynamic SQL query for JSON data retrieval
SET @query = N'SELECT (SELECT ' + @columns +
' FROM ' + @SchemaAndTableName +
' WHERE Deleted = 0';
-- Append the condition for the object ID if provided
-- (flexibility to return a list or a single result)
IF @ObjectId IS NOT NULL
BEGIN
SET @query = @query + ' AND ObjectId = ' + CONVERT(NVARCHAR(50), @ObjectId);
END
-- Complete the SQL query to return JSON data
SET @query = @query + ' FOR JSON AUTO, INCLUDE_NULL_VALUES) AS JsonResult';
-- Execute the dynamic SQL query
EXEC sp_executesql @query;
END