Skip to content

Add columns in migrations

Arc’s column helpers let a migration select the corresponding database type for PostgreSQL, SQL Server, or SQLite. The mappings below show where those providers differ.

These extension methods are similar to the Common Column Types used when creating tables, but are specifically designed for AddColumn operations in migrations.

The following methods extend MigrationBuilder. Import Cratis.Arc.EntityFrameworkCore; AddJsonColumn<T> additionally requires Cratis.Arc.EntityFrameworkCore.Json. Examples are migration fragments using your application’s context and schema; they do not run independently.

Extension Method Description Supported Types Parameters
AddStringColumn() Adds a string column with appropriate database-specific type (VARCHAR/NVARCHAR/TEXT) string name, table, maxLength (int?, optional), nullable (bool, default: true), defaultValue (string?, optional), schema (string?, optional)
AddNumberColumn<T>() Adds a numeric column with appropriate database-specific type for any numeric type char, byte, sbyte, short, ushort, int, uint, long, ulong, float, double, decimal name, table, nullable (bool, default: true), defaultValue (object?, optional), schema (string?, optional)
AddBoolColumn() Adds a boolean column with appropriate database-specific type (BOOLEAN/BIT/INTEGER) bool name, table, nullable (bool, default: true), defaultValue (bool, default: false), schema (string?, optional)
AddAutoIncrementColumn() Adds an auto-incrementing integer column with appropriate database-specific annotations int name, table, schema (string?, optional)
AddGuidColumn() Adds a GUID/UUID column with appropriate database-specific type (UUID/UNIQUEIDENTIFIER/BLOB) Guid name, table, nullable (bool, default: true), schema (string?, optional)
AddDateTimeOffsetColumn() Adds a DateTimeOffset column with appropriate database-specific type (TIMESTAMPTZ/DATETIMEOFFSET/TEXT) DateTimeOffset name, table, nullable (bool, default: true), schema (string?, optional)
AddPointColumn() Declares a spatial/text column; not a model converter Point name, table, nullable: true, schema
AddLineStringColumn() Declares a spatial/text column; not a model converter LineString name, table, nullable: true, schema
AddPolygonColumn() Declares a spatial/text column; not a model converter Polygon name, table, nullable: true, schema
AddJsonColumn<T>() Declares a non-nullable JSON column JSON property type name, table, schema

Geometry helpers select geometry(Point, 4326), geometry(LineString, 4326), or geometry(Polygon, 4326) on PostgreSQL; geography on SQL Server; and TEXT on SQLite. They do not install spatial provider mappings or make plain JSON string converters compatible with native spatial columns. See the storage-path matrix.

On PostgreSQL, AddJsonColumn<T>() selects jsonb and adds a JSON CHECK constraint and a GIN index using jsonb_path_ops. The column, constraint, and index are emitted as separate, terminated commands. The index uses a ::jsonb expression, so an explicitly overridden text or json store type also works. Generated constraint and index names are quoted; names exceeding PostgreSQL’s 63-byte identifier limit are shortened with a stable hash suffix.

Adding non-nullable columns to populated tables may require a default or a staged backfill. Verify the generated SQL, model compatibility, permissions, and provider restrictions before applying any migration.

The extension methods automatically select the appropriate SQL type based on the database provider. See Common Column Types for detailed type mappings for each database.

[DbContext(typeof(MyDbContext))]
[Migration($"MyContext_{nameof(v1_1_0)}")]
public class v1_1_0 : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
// Add string column with length limit
migrationBuilder.AddStringColumn(
name: "Email",
table: "Users",
maxLength: 255,
nullable: false);
// Add unlimited text column with default value
migrationBuilder.AddStringColumn(
name: "Description",
table: "Products",
defaultValue: "No description provided");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(name: "Email", table: "Users");
migrationBuilder.DropColumn(name: "Description", table: "Products");
}
}
protected override void Up(MigrationBuilder migrationBuilder)
{
// Add integer column
migrationBuilder.AddNumberColumn<int>(
name: "ViewCount",
table: "Articles",
defaultValue: 0,
nullable: false);
// Add decimal column for currency
migrationBuilder.AddNumberColumn<decimal>(
name: "Price",
table: "Products",
nullable: true);
// Add double column for ratings
migrationBuilder.AddNumberColumn<double>(
name: "Rating",
table: "Reviews",
defaultValue: 0.0,
nullable: false);
}
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddBoolColumn(
name: "IsActive",
table: "Users",
defaultValue: true,
nullable: false);
migrationBuilder.AddBoolColumn(
name: "IsDeleted",
table: "Orders",
defaultValue: false);
}
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddGuidColumn(
name: "ExternalId",
table: "Users",
nullable: false);
migrationBuilder.AddGuidColumn(
name: "CorrelationId",
table: "Events",
nullable: true);
}
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddDateTimeOffsetColumn(
name: "LastLoginAt",
table: "Users",
nullable: true);
migrationBuilder.AddDateTimeOffsetColumn(
name: "CreatedAt",
table: "Orders",
nullable: false);
}
protected override void Up(MigrationBuilder migrationBuilder)
{
// Add auto-increment column (useful for adding sequence numbers to existing tables)
migrationBuilder.AddAutoIncrementColumn(
name: "SequenceNumber",
table: "AuditLog");
}

For databases that support schemas (PostgreSQL, SQL Server), you can specify the schema:

protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddStringColumn(
name: "Department",
table: "Employees",
schema: "hr",
maxLength: 100,
nullable: false);
}

Using these extension methods provides several advantages:

  • Database Portability: Write migrations once and support multiple database providers
  • Type Safety: Strongly typed methods reduce errors
  • Consistency: Ensures consistent type mapping across your application
  • Simplified Syntax: Less boilerplate code compared to manually specifying types
  • Maintainability: Centralized type mapping logic makes updates easier