Reports using C#  

Highly Customizable Reporting Architecture (Metadata → SQL → API → UI)

Building a flexible reporting system is one of the most recurring challenges in enterprise applications. Different departments need different formats, filters, rules, and visual outputs. Hard-coding reports creates long maintenance cycles and slows down delivery.

A Metadata-driven Reporting Architecture solves this problem. Instead of writing code for every report, we define reports using metadata (stored in a database), and the system automatically generates:

  • SQL queries

  • API responses

  • UI table or chart rendering

This approach allows non-developers (admins, analysts) to configure and publish new reports without modifying backend or frontend code.

Why Metadata-Driven Reporting?

Traditional reporting involves:

  • Developers writing SQL queries.

  • Deployments for every report change.

  • Difficult scaling when the number of reports reaches hundreds.

A metadata-based approach offers:

  • No deployment required for new reports.

  • Dynamic filtering, sorting, pagination.

  • Data-level access control (tenant, role, user).

  • Standardized performance and caching rules.

Core Concepts

A metadata-driven report framework includes:

LayerPurpose
Metadata StorageDefines report structure, filters, joins, fields
Query EngineConverts metadata into executable SQL
API LayerRuns the queries securely and efficiently
Frontend RendererAuto-builds UI (table or chart) from metadata
Access RulesControls visibility per user or role

Architecture Overview

Metadata DB
     |
     v
Query Builder Engine
     |
     v
API Execution and Caching
     |
     v
Angular UI Renderer (Table/Chart/Export)

Workflow Diagram

User Requests Report
        |
        vLoad Report Metadata
        |
        vBuild SQL Using Query Builder
        |
        vExecute and Cache Results
        |
        vRender UI Auto (Grid or Chart)
        |
        vOptional Export (CSV, PDF, Excel)

Flowchart

START|
  v
Select Report from UI
  |
  v
Fetch Metadata from DB
  |
  v
Metadata valid?
  |--NO--> Show error|
  YES
  |
  v
Generate SQL Query dynamically
  |
  v
Execute Query with filters
  |
  v
Return paginated result set|
  v
Render table or chart dynamically
  |END

Metadata Model Design

Report Table

CREATE TABLE Reports (
    ReportId UNIQUEIDENTIFIER PRIMARY KEY,
    Name NVARCHAR(200),
    Description NVARCHAR(MAX),
    BaseTable NVARCHAR(200),
    ChartType NVARCHAR(50) NULL,
    IsActive BIT DEFAULT 1
);

Field Metadata

CREATE TABLE ReportFields (
    FieldId UNIQUEIDENTIFIER PRIMARY KEY,
    ReportId UNIQUEIDENTIFIER,
    ColumnName NVARCHAR(200),
    DisplayName NVARCHAR(200),
    DataType NVARCHAR(50),
    IsVisible BIT DEFAULT 1,
    IsSortable BIT DEFAULT 1,
    IsFilterable BIT DEFAULT 1,
    Aggregation NVARCHAR(100) NULL
);

Filter Metadata

CREATE TABLE ReportFilters (
    FilterId UNIQUEIDENTIFIER PRIMARY KEY,
    ReportId UNIQUEIDENTIFIER,
    FieldName NVARCHAR(200),
    FilterType NVARCHAR(50), -- textbox, dropdown, dateRange
    DefaultValue NVARCHAR(200) NULL
);

Dynamic SQL Generation (C# Sample)

public string BuildQuery(ReportDefinition report, Dictionary<string, string> filters)
{
    var select = string.Join(", ",
        report.Fields.Where(f => f.IsVisible).Select(f => f.ColumnName));

    var sql = $"SELECT {select} FROM {report.BaseTable}";

    if(filters.Any())
    {
        var where = string.Join(" AND ",
            filters.Select(f => $"{f.Key} = @{f.Key}"));

        sql += $" WHERE {where}";
    }

    return sql;
}

API Endpoint

[HttpPost("execute")]
public async Task<IActionResult> Execute([FromBody] ReportRequest request)
{
    var metadata = await _metadataService.GetReport(request.ReportId);
    var sql = _queryBuilder.BuildQuery(metadata, request.Filters);
    var result = await _db.QueryAsync(sql, request.Filters);
    return Ok(result);
}

Angular UI Renderer

The frontend dynamically builds columns and filters based on metadata.

loadReport(reportId: string) {
  this.http.post('/api/reports/execute', { reportId, filters: {} })
    .subscribe(result => {
      this.columns = result.metadata.fields;
      this.rows = result.data;
    });
}

Rendering Modes

The UI should automatically detect and render based on metadata:

ModeTrigger
TableDefault
ChartMetadata.ChartType not null
Pivot ViewIf Aggregations exist
Export ModeUser clicks Export to PDF/Excel

Access Control

Store role-based visibility rules:

CREATE TABLE ReportPermissions (
    ReportId UNIQUEIDENTIFIER,
    Role NVARCHAR(200),
    CanExecute BIT DEFAULT 1,
    CanExport BIT DEFAULT 1
);

API checks permissions before execution.

Caching Strategy

To prevent repeated heavy query execution:

  • Cache by ReportId + Filter hash.

  • Set TTL based on request frequency.

  • Invalidate cache when data source changes.

Performance Considerations

  • Always use pagination.

  • Limit exported row size.

  • Validate SQL injection by restricting metadata access.

  • Offload analytical reports to replicas or warehouses.

Real-World Use Cases

  • ERP with configurable dashboards.

  • Finance reconciliation reporting.

  • Manufacturing KPIs and operational metrics.

  • Multi-tenant SaaS usage monitoring.

Best Practices

  • Audit report execution logs.

  • Never expose raw column names to UI.

  • Generate preview before allowing publish.

  • Allow cloning and versioning of reports.

Conclusion

A metadata-driven reporting architecture allows platforms to scale reporting without repeated development cycles. With SQL generation, API abstraction, and dynamic UI rendering, business teams gain direct ownership of report creation while developers focus on core platform logic.