EFBuilder
Working with EF Core code-first often means writing repetitive, boilerplate code that you have to slow down and think about.
Imagine defining your EF Core entities using a concise, expressive syntax where most of the C# details can be filled in automatically, letting you focus on your model's intent.
Meet EFBuilder — a Markdown interpreter that generates EF Core code-first entities for you.





Syntax Examples
The markdown syntax is easy to get started with, but has some advanced features.
The first line of the markdown is always the entity name. All subsequent lines are properties. For data types, you can use a CLR type followed by an optional size in parentheses. A trailing question mark indicates that the property is nullable.
Markdown Input:
Customer
FirstName string(50)
LastName string(50)
Email string(50)?
@tag1
@tag2
You can also add optional tags by adding lines starting with '@' followed by a word. These appear in the UI for navigation purposes.
Generated C# Output:
using Generated;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Generated;
public class Customer
{
public string FirstName { get; set; } = default!;
public string LastName { get; set; } = default!;
public string? Email { get; set; }
}
public class CustomerConfiguration : IEntityTypeConfiguration<Customer>
{
public void Configure(EntityTypeBuilder<Customer> builder)
{
builder.Property(x => x.FirstName).IsRequired().HasMaxLength(50);
builder.Property(x => x.LastName).IsRequired().HasMaxLength(50);
builder.Property(e => e.Email).HasMaxLength(50);
}
}
Let's add an Order
table to our model. To reference the Customer
table, simply give it a CustomerId
property with no data type. The referencing data type is inferred from the primary key of the Customer
table.
Markdown Input:
Order
CustomerId
Date DateTime
Generated Order Class:
using Generated;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Generated;
public class Order
{
public int CustomerId { get; set; }
public DateTime Date { get; set; }
public Customer? Customer { get; set; }
}
public class OrderConfiguration : IEntityTypeConfiguration<Order>
{
public void Configure(EntityTypeBuilder<Order> builder)
{
builder.HasOne(e => e.Customer).WithMany(e => e.Orders).HasForeignKey(x => x.CustomerId).OnDelete(DeleteBehavior.Restrict);
}
}
Updated Customer Class (automatically gets Orders collection):
public class Customer
{
public string FirstName { get; set; } = default!;
public string LastName { get; set; } = default!;
public string? Email { get; set; }
public ICollection<Order> Orders { get; set; } = [];
}
To create a composite unique index, prefix property names with the hash symbol #
.
Markdown Input:
OrderLine
#OrderId
#ProductId
Quantity int
Generated Configuration (key parts):
// some statements omitted for brevity
builder.HasIndex(e => new { e.OrderId, e.ProductId }).IsUnique();
To create a unique index on a single column, suffix the property name with the hash symbol #
.
Markdown Input:
CustomerPhone
CustomerId
PhoneNumber# string(10)
Generated C# Output (note the unique PhoneNumber property):
public class CustomerPhone
{
public int CustomerId { get; set; }
public string PhoneNumber { get; set; } = default!;
public Customer? Customer { get; set; }
}
public class CustomerPhoneConfiguration : IEntityTypeConfiguration<CustomerPhone>
{
public void Configure(EntityTypeBuilder<CustomerPhone> builder)
{
builder.Property(x => x.PhoneNumber).IsRequired().HasMaxLength(10);
builder.HasIndex(e => e.PhoneNumber).IsUnique();
builder.HasOne(e => e.Customer).WithMany(e => e.CustomerPhones).HasForeignKey(x => x.CustomerId).OnDelete(DeleteBehavior.Restrict);
}
}
To create a one-to-one relationship, make the foreign key property unique -- either with a composite unique index or an individually unique column.
Markdown Input:
OneTimeOrderInfo
#OrderId
Data string(500)
Generated Configuration (note the HasOne ... WithOne syntax):
// some details omitted
public class OneTimeOrderInfoConfiguration : IEntityTypeConfiguration<OneTimeOrderInfo>
{
public void Configure(EntityTypeBuilder<OneTimeOrderInfo> builder)
{
builder.Property(x => x.Data).IsRequired().HasMaxLength(500);
builder.HasIndex(e => e.OrderId).IsUnique();
builder.HasOne(e => e.Order).WithOne(e => e.OneTimeOrderInfo).HasForeignKey<OneTimeOrderInfo>(x => x.OrderId).OnDelete(DeleteBehavior.Restrict);
}
}
You can also reference another entity as the data type. EFBuilder will infer the foreign key property and related collections. Nullability options apply.
Markdown Input:
ProductMap
#FirstProductId Product
#SecondProductId Product
OptionalProductId Product?
Generated C# Output:
public class ProductMap
{
public int FirstProductId { get; set; }
public int SecondProductId { get; set; }
public int? OptionalProductId { get; set; }
public Product? FirstProduct { get; set; }
public Product? SecondProduct { get; set; }
public Product? OptionalProduct { get; set; }
}
public class ProductMapConfiguration : IEntityTypeConfiguration<ProductMap>
{
public void Configure(EntityTypeBuilder<ProductMap> builder)
{
builder.HasIndex(e => new { e.FirstProductId, e.SecondProductId }).IsUnique();
builder.HasOne(e => e.FirstProduct).WithMany(e => e.ProductMaps).HasForeignKey(x => x.FirstProductId).OnDelete(DeleteBehavior.Restrict);
builder.HasOne(e => e.SecondProduct).WithMany(e => e.ProductMaps).HasForeignKey(x => x.SecondProductId).OnDelete(DeleteBehavior.Restrict);
builder.HasOne(e => e.OptionalProduct).WithMany(e => e.ProductMaps).HasForeignKey(x => x.OptionalProductId).OnDelete(DeleteBehavior.Restrict);
}
}
By default, collection navigation and DbSet properties are named after the pluralized entity name. You can customize this by adding a <
symbol followed by the desired property name.
Customer <AllCustomers
FirstName string(50)
LastName string(50)
Email string(50)?
DbContext output:
// db context code
public DbSet<Customer> AllCustomers { get; set; }
Collection properties can be customized in a similar way. Here, the CustomerId
property has a collection name CustomerOrders
:
Order
CustomerId <CustomerOrders
Date DateTime
This causes the Customer
entity class to name its Order
collection property CustomerOrders
:
// generated entity code
public class Customer
{
public string FirstName { get; set; } = default!;
public string LastName { get; set; } = default!;
public string? Email { get; set; }
public ICollection<CustomerPhone> CustomerPhones { get; set; } = [];
public ICollection<Order> CustomerOrders { get; set; } = [];
}
You can apply a base class to an entity by suffixing the entity name with a colon followed by the base class name.
Customer : BaseTable <Customers
FirstName
LastName
Address
Generated C# output:
public class Customer : BaseTable
{
public string FirstName { get; set; } = default!;
public string LastName { get; set; } = default!;
public string Address { get; set; } = default!;
}