csharp sql database entity

C# Class to SQL Table: Generate CREATE TABLE from Your Entities

How to scaffold SQL CREATE TABLE statements from C# entity classes for SQL Server, MySQL, and PostgreSQL — what gets mapped, what doesn't, and how to use the output.

· ByteKiln

Writing a CREATE TABLE statement by hand from a C# class is mechanical work. Each property becomes a column, the types need to be translated, primary keys and nullability need to be inferred, and you need to pick the right syntax for your target database. It’s not hard, but it’s slow and repetitive.

The C# to SQL converter automates this translation. You paste the class, pick your dialect, and get a ready-to-use DDL statement.


A Complete Example

Input:

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public decimal Price { get; set; }
    public int StockQuantity { get; set; }
    public bool IsActive { get; set; }
    public DateTime CreatedAt { get; set; }
    public DateTime? UpdatedAt { get; set; }
    public Guid ExternalId { get; set; }
}

Output (PostgreSQL dialect):

CREATE TABLE product (
  id SERIAL PRIMARY KEY,
  name TEXT NOT NULL,
  description TEXT NOT NULL,
  price NUMERIC(18, 2) NOT NULL,
  stock_quantity INTEGER NOT NULL,
  is_active BOOLEAN NOT NULL,
  created_at TIMESTAMP NOT NULL,
  updated_at TIMESTAMP,
  external_id UUID NOT NULL
);

Output (T-SQL / SQL Server):

CREATE TABLE [Product] (
  [Id] INT IDENTITY(1,1) PRIMARY KEY,
  [Name] NVARCHAR(255) NOT NULL,
  [Description] NVARCHAR(255) NOT NULL,
  [Price] DECIMAL(18, 2) NOT NULL,
  [StockQuantity] INT NOT NULL,
  [IsActive] BIT NOT NULL,
  [CreatedAt] DATETIME2 NOT NULL,
  [UpdatedAt] DATETIME2 NULL,
  [ExternalId] UNIQUEIDENTIFIER NOT NULL
);

Two different outputs from the same input — each using the correct syntax, type names, and quoting conventions for its dialect.


Type Mapping: C# to SQL

Here’s how the most common C# types translate across dialects:

C# typeSQL ServerPostgreSQLMySQL
intINTINTEGERINT
longBIGINTBIGINTBIGINT
stringNVARCHAR(255)TEXTVARCHAR(255)
boolBITBOOLEANTINYINT(1)
decimalDECIMAL(18,2)NUMERIC(18,2)DECIMAL(18,2)
doubleFLOATDOUBLE PRECISIONDOUBLE
floatREALREALFLOAT
DateTimeDATETIME2TIMESTAMPDATETIME
DateTimeOffsetDATETIMEOFFSETTIMESTAMPTZDATETIME
GuidUNIQUEIDENTIFIERUUIDCHAR(36)
byte[]VARBINARY(MAX)BYTEABLOB

Nullable types (e.g., int?, DateTime?) map to the same SQL type but without NOT NULL.


Primary Key Inference

The converter applies a simple rule: a property named Id or <ClassName>Id is treated as the primary key.

  • int or long primary key → identity/serial semantics: IDENTITY(1,1) (T-SQL), SERIAL (PostgreSQL), AUTO_INCREMENT (MySQL)
  • Guid primary key → no auto-generation; Guids are typically generated in application code
public class Customer
{
    public int CustomerId { get; set; }  // → PRIMARY KEY with identity
    public string Name { get; set; }
}
public class Session
{
    public Guid Id { get; set; }         // → PRIMARY KEY, no auto-increment
    public DateTime StartedAt { get; set; }
}

Column Naming Conventions

The converter applies dialect-appropriate naming:

  • PostgreSQLsnake_case (PostgreSQL convention, and the default Npgsql behavior)
  • T-SQL / SQL ServerPascalCase with bracket quoting
  • MySQLsnake_case with backtick quoting

If your team uses a different naming convention, treat the output as a starting point and adjust column names.


What the Three Dialects Produce Differently

T-SQL (SQL Server)

  • Bracket quoting: [ColumnName]
  • String type: NVARCHAR(255) (Unicode by default)
  • Bool: BIT
  • Guid: UNIQUEIDENTIFIER
  • Date: DATETIME2
  • Identity: IDENTITY(1,1)

PostgreSQL

  • Double-quote or unquoted column names; snake_case is standard
  • String type: TEXT (no length limit)
  • Bool: BOOLEAN
  • Guid: UUID
  • Date: TIMESTAMP or TIMESTAMPTZ
  • Serial: SERIAL (or GENERATED ALWAYS AS IDENTITY in newer PostgreSQL)

MySQL

  • Backtick quoting: `column_name`
  • String type: VARCHAR(255)
  • Bool: TINYINT(1) (MySQL doesn’t have a true boolean; TINYINT(1) is the convention)
  • Guid: CHAR(36) (MySQL has no native UUID type in older versions)
  • Date: DATETIME
  • Auto increment: AUTO_INCREMENT

Practical Uses

Bootstrapping a new table

You’ve designed a domain entity in C#. Before writing migrations or using EF Core, you want to see what the table should look like. Paste the class, pick your database, and get the DDL. Review it, adjust column lengths or types, then use it in your migration script or apply it directly.

Reverse engineering for documentation

You have existing C# entities and no database documentation. Generate the corresponding CREATE TABLE for each entity. This is often faster than navigating a database schema viewer when you’re trying to explain the data model to a new team member.

Database-first teams working with C# code

If your database team is writing the schema and you need to make sure the C# model aligns, run both through the tool (C# → SQL and compare, or SQL → check manually) to confirm the type mappings are consistent.

Cross-database migrations

You’re moving from SQL Server to PostgreSQL. Paste the T-SQL entity class and generate PostgreSQL output. Use it as a starting point for the PostgreSQL migration script.


Known Limitations

Navigation properties are not mapped. Entity Framework navigation properties like:

public virtual ICollection<OrderItem> Items { get; set; }
public virtual Customer Customer { get; set; }

…are not included in the DDL output. The generator focuses on scalar properties. Foreign key columns are not automatically inferred from navigation properties.

No indexes, constraints, or defaults. The output is a basic CREATE TABLE. Indexes, UNIQUE constraints, CHECK constraints, and column defaults are not generated.

No EF Core annotations. [MaxLength], [Column("custom_name")], [Table("custom_table")] are not read. The output uses the property name directly.

Complex types and value objects (like EF Core owned entities) are not expanded.

For production DDL, start with the generated output and add the above as needed.


Workflow Tip: Format the Output

The generated SQL is functional but not always perfectly indented for your team’s style. After generating, paste the output into the SQL Formatter and pick your dialect — this gives you cleanly indented DDL that’s ready for a migration file or a pull request.


If your data originates from a JSON API rather than a C# model, the JSON to SQL guide covers the same DDL scaffolding from a JSON object. And if you need sample JSON payloads from the same C# entities, the C# to JSON guide generates those in one step.

The C# to SQL converter is most useful during the design phase — when you’re thinking about data models and want to see the database side of the picture without switching contexts. Generate the DDL, run it against a local database, and iterate on the C# model and schema together.