Photo from Unsplash
Originally Posted On: https://medium.com/@sharathreddydopathi/ai-powered-chatbot-for-natural-language-to-sql-querying-with-blazor-and-postgresql-53317cb60f01
Introduction
Imagine being able to ask questions about your data in plain English and get instant answers — without writing a single line of SQL. In this article, I’ll share how I built an AI-powered chatbot using Blazor Server, OpenAI’s GPT-3.5-turbo, and PostgreSQL. The app lets users upload CSV and PDF files (even very large ones), automatically creates database tables, and allows natural language querying with live results and also export the results into a pdf report using IronPDF — all in a modern, responsive web UI.
Why Blazor?
Blazor Server is a powerful .NET framework for building interactive web applications with C#. It offers real-time UI updates, seamless backend integration, and a component-based architecture. For this project, Blazor provided the perfect balance of productivity, performance, and flexibility.
Key Features
- Large CSV & PDF Uploads: Effortlessly upload CSV files with hundreds of thousands of rows, or large PDF documents. The app is optimized for high-volume data and document handling.
- Automatic Table Creation: The app infers column types and creates tables on the fly, handling large datasets efficiently with batched inserts and robust type checking.
- AI-Powered SQL Generation: Users type natural language questions; the app uses OpenAI’s GPT-3.5-turbo to generate SQL queries tailored to the uploaded data.
- Live Results: The generated SQL and the resulting data table are displayed instantly in the UI.
- PDF Export: The query results can now be exported into a pdf report by IronPDF.
- PDF Q&A: Upload PDFs and ask questions about their content, with answers generated by the AI model.
- Modern UI: Built with Bootstrap 5 and custom CSS for a clean, responsive experience.

How It Works
1. Uploading Large Data
Users start by uploading one or more CSV files — even those with hundreds of thousands of rows. The app reads the files, infers column types (e.g., text, number, date), and creates corresponding tables in PostgreSQL. For large files, data is inserted in batches for speed and reliability, and incompatible rows are automatically skipped to ensure smooth imports. PDF files can also be uploaded for document-based Q&A, with support for large documents.
CSV Upload and Table Creation (C#):
private async Task CreateDatabaseTablesAsync()
{
IsProcessing = true;
StateHasChanged();
try
{
foreach (var file in UploadedFiles.Where(f => f.Name.EndsWith(".csv", StringComparison.OrdinalIgnoreCase)))
{
var sanitizedFileName = SanitizeFileName(file.Name);
using var stream = file.OpenReadStream(MaxFileSize);
using var reader = new StreamReader(stream);
var csvContent = await reader.ReadToEndAsync();
ProcessCsvFile(csvContent, sanitizedFileName);
}
SuccessMessage = "File Processed successfully!";
ShowSuccessMessage = true;
StateHasChanged();
}
catch (Exception ex)
{
SuccessMessage = $"Error: {ex.Message}";
}
IsProcessing = false;
StateHasChanged();
}
Efficient Batched Insert (C#):
private void BatchedInsertCsvToPostgres(string[] headers, List<string[]> rows, string tableName, List<string> columnTypes, int batchSize = 2000)
{
using var connection = new Npgsql.NpgsqlConnection(GetConnectionString());
connection.Open();
var columnNames = headers.Select(SanitizeColumnName).ToArray();
for (int i = 0; i < rows.Count; i += batchSize)
{
var batchRows = rows.Skip(i).Take(batchSize).ToList();
var valuePlaceholders = new List<string>();
var parameters = new List<Npgsql.NpgsqlParameter>();
for (int r = 0; r < batchRows.Count; r++)
{
var row = batchRows[r];
var rowPlaceholders = new List<string>();
bool rowIsValid = true;
for (int c = 0; c < columnNames.Length; c++)
{
var paramName = $"@p_{r}_{c}";
object value = DBNull.Value;
if (row.Length > c && !string.IsNullOrWhiteSpace(row[c]))
{
var type = columnTypes[c];
var cell = row[c].Trim();
try
{
value = type switch
{
"DOUBLE PRECISION" => double.TryParse(cell, out var d) ? d : throw new Exception(),
"BOOLEAN" => bool.TryParse(cell, out var b) ? b : throw new Exception(),
"DATE" => DateTime.TryParse(cell, out var dt) ? dt.Date : throw new Exception(),
"TIMESTAMP" => DateTime.TryParse(cell, out var ts) ? ts : throw new Exception(),
_ => cell
};
}
catch
{
rowIsValid = false;
break;
}
}
rowPlaceholders.Add(paramName);
parameters.Add(new Npgsql.NpgsqlParameter(paramName, value));
}
if (rowIsValid)
valuePlaceholders.Add($"({string.Join(", ", rowPlaceholders)})");
else
parameters.RemoveRange(parameters.Count - rowPlaceholders.Count, rowPlaceholders.Count);
}
if (valuePlaceholders.Count == 0)
continue;
var sql = $"INSERT INTO {tableName} ({string.Join(", ", columnNames)}) VALUES {string.Join(", ", valuePlaceholders)};";
using var cmd = new Npgsql.NpgsqlCommand(sql, connection);
cmd.Parameters.AddRange(parameters.ToArray());
cmd.ExecuteNonQuery();
}
}

2. Natural Language Querying
Once the data is uploaded, users can ask questions in plain English, such as:
• “Show me all customers from California.”
• “What is the average order value in 2024?”
The app sends the question and table schema to OpenAI’s API, which returns a SQL query. The query is executed against the database, and the results are shown in a formatted table.
Natural Language to SQL (C#):
private async Task<string> QueryOpenAiAsync(string nlQuery, string tableInfo)
{
var apiKey = Configuration["OpenAI:ApiKey"];
if (string.IsNullOrEmpty(apiKey))
throw new InvalidOperationException("OpenAI API key is not configured.");
var prompt = $@"
You are an AI that converts natural language questions into SQL queries.
Available tables and their columns:
{tableInfo}
Generate an SQL query that answers the following question:
{nlQuery}
Ensure to use appropriate JOINs if required.
SQL Query:
";
var client = new HttpClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
var requestBody = new
{
model = "gpt-3.5-turbo",
messages = new[] { new { role = "user", content = prompt } },
max_tokens = 150
};
var response = await client.PostAsJsonAsync("https://api.openai.com/v1/chat/completions", requestBody);
response.EnsureSuccessStatusCode();
var resultJson = await response.Content.ReadAsStringAsync();
var result = System.Text.Json.JsonDocument.Parse(resultJson);
var query = result.RootElement
.GetProperty("choices")[0]
.GetProperty("message")
.GetProperty("content")
.GetString()
?.Trim();
return query ?? string.Empty;
}

3. Converting Query Results into a PDF report using IronPDF
Once the chatbot retrieves query results, users often want to export or share that data. IronPDF integrates seamlessly with Blazor to generate downloadable PDF reports directly from the SQL results.
Query Results to PDF export(IronPDF):
using IronPdf;
public async Task<byte[]> ExportResultsToPdf(DataTable queryResults, string userQuestion)
{
var html = $"<h1>Query Report</h1><p><strong>Question:</strong> {userQuestion}</p>";
html += ConvertDataTableToHtml(queryResults);
var renderer = new ChromePdfRenderer();
var pdf = renderer.RenderHtmlAsPdf(html);
return pdf.BinaryData;
}
This allows non-technical users to not only query the database in plain English but also download polished reports without leaving the chatbot interface. The library uses a Chromium rendering engine, so that the Blazor component styles carry over to the PDF output consistently.
4. PDF Question Answering
If a PDF is uploaded, users can ask questions about its content. The app extracts text from the PDF and uses the AI model to generate answers based on the document.
PDF Extraction and Q&A (C#):
private string ExtractTextFromPdf(Stream pdfStream)
{
try
{
using var pdf = UglyToad.PdfPig.PdfDocument.Open(pdfStream);
var text = string.Join("n", pdf.GetPages().Select(p => p.Text));
return text;
}
catch
{
return "Could not extract text from PDF.";
}
}

Technologies Used
- Blazor Server (.NET 8)
- Bootstrap 5
- Npgsql (PostgreSQL C# driver)
- OpenAI API (GPT-3.5-turbo)
- UglyToad.PdfPig (for PDF text extraction)
- IronPDF(for pdf export)
- C#
Demo Vidoes
Challenges & Solutions
- Efficient Large Data Handling: Inserting large datasets efficiently was solved with batched parameterized inserts, skipping incompatible rows to avoid errors. The app can handle CSVs with hundreds of thousands of rows without timing out.
- Type Inference: Automatically determining the correct SQL data type for each CSV column required careful sampling and validation.
- AI Query Generation: Prompt engineering was key to getting accurate SQL from the AI model, especially for complex queries and joins.
- PDF Export: I couldn’t find a proper library that lets export the query results into pdf report. But IronPDF made that task very easy by easily integrating with Blazor.
- PDF Handling: Extracting clean text from large PDFs and handling document Q&A was achieved with PdfPig and smart text chunking.
Conclusion
This project demonstrates how modern AI and .NET technologies can make data exploration accessible to everyone — even with massive datasets and documents. By combining Blazor, PostgreSQL, OpenAI, and IronPDF you can empower users to interact with their data naturally and intuitively.
You can find the repo for this project on my github: https://github.com/SHARATH00/ChatBot-AI