Why OnModelCreating Is My EF Core Superpower (And Why I Keep It Simple)
How One Method Gives You Total Control Over Your Database—Without the Jargon
Entity Framework Core (EF Core) is a beast of a tool for wrangling databases in .NET, and at its heart lies something called OnModelCreating. If you’re like me, you might’ve started with those little attribute tags—Data Annotations—thinking they’re the uncomplicated way to tell EF Core what your database should look like. But here’s the thing: after digging deep, I’m sold on OnModelCreating. It’s the real boss, the final say, the place where you can make your data model and do exactly what you want without surprises. I’m not here to throw around fancy technical terms—I’d rather talk straight, normal words for normal people building real stuff. So, let’s unpack why OnModelCreating is my go-to, how it handles the basics like columns and keys, and why it’s worth your time.
The Problem with the Little Tags
Picture this: you’ve got a User class, and you slap a
[Column("first_name")]
public string? FirstName { get; set; }
tag on a property called FirstName. You’re thinking, “Cool, my database column will be first_name.” Then you run your app, check the database, and—bam—it’s called usr_first_name instead. You’re scratching your head, wondering what went wrong. Turns out, somewhere in your DbContext, there’s this method called OnModelCreating that said, “Nah, I’ve got a better idea,” and overruled your tag. That’s the first lesson I learned the hard way: those little tags—Data Annotations—aren’t the final word. OnModelCreating is.
See, Data Annotations are like sticky notes you put in your classes. They’re simple, sure. You write
[Table("usr_users")]
or
[MaxLength(50)]
and EF Core gets the hint. But they’re weak. They live right there in your code, next to your properties, which feels nice and tidy—until you need something more complicated. Want a column that’s calculated on the fly, like a full name stitched from first and last? Good luck with annotations. Want to map a database view that doesn’t even have a key? Nope, can’t do it. That’s where OnModelCreating steps in, and it’s why I’ve ditched the tags for good.
OnModelCreating: The Control Room
Think of OnModelCreating as the control room for your database. It’s a method you override in your DbContext—that’s the class that ties your code to your database—and it’s where you tell EF Core, “Here’s how everything fits together.” It uses a chainable setup that’s like giving directions: “Take this property, name its column this, set its type to that, and make it required.” No need for cryptic jargon—it’s straightforward once you get the hang of it.
Here’s why I love it: it’s the boss. If there’s a fight between what a tag says and what OnModelCreating says, OnModelCreating wins every time. That means no more guessing games. I can put all my rules in one place, see them clearly, and know they’ll stick. Plus, it can do stuff those tags can only dream of. Let’s break it down with some real examples—stuff I’ve wrestled with and figured out.
Shaping Columns: Total Control
Columns are the building blocks of your tables, and OnModelCreating lets you shape them however you want. Say I’ve got a User class with a FirstName property. Here’s how I’d set it up:
modelBuilder.Entity<User>(entity =>
{
entity.ToTable("usr_users");
entity.Property(u => u.FirstName)
.HasColumnName("usr_first_name")
.HasColumnType("varchar(50)")
.HasMaxLength(50)
.IsRequired();
});
This says: “Put User in a table called usr_users, and make the FirstName column usr_first_name, a 50-character string that can’t be empty.” Simple, right? But it gets better. What if I want a column to have a default value, like the date something was created? Or one that’s calculated automatically? Check this out for an Expense class:
modelBuilder.Entity<Expense>(entity =>
{
entity.ToTable("exp_expenses");
entity.Property(e => e.CreationDate)
.HasColumnName("exp_creation_date")
.HasDefaultValueSql("GETUTCDATE()");
entity.Property(e => e.FullAmount)
.HasColumnName("exp_full_amount")
.HasComputedColumnSql("[exp_amount] * 1.1");
});
Now, CreationDate fills in with the current date if I don’t set it, and FullAmount is calculated as 10% more than Amount—like a tax or fee—right in the database. Try doing that with a tag. You can’t. This is why OnModelCreating is my jam—it’s got the power to handle the real world.
Ignoring Stuff: Keeping It Clean
Not every property in your class needs to end up in the database. Maybe I’ve got a FullName that’s just FirstName plus LastName for display purposes. I don’t want a column for that—it’s not worth storing. With OnModelCreating, I can tell EF Core to skip it:
modelBuilder.Entity<User>(entity =>
{
entity.ToTable("usr_users");
entity.Property(u => u.FirstName).HasColumnName("usr_first_name");
entity.Property(u => u.LastName).HasColumnName("usr_last_name");
entity.Ignore(u => u.FullName);
});
FullName stays in my code for convenience, but it doesn’t clutter my database. If I used a tag like
[NotMapped]
it’d work too—but then I’m back to scattering rules across my classes. With OnModelCreating, it’s all in one spot, and I don’t have to worry about some tag getting ignored because I wrote something different here. It’s the final word, remember?
Keys: Locking It Down
Keys are how EF Core knows what’s unique—like the ID of a user or a combo of user and role in a join table. With OnModelCreating, I can set them up exactly how I need:
Simple Key:
modelBuilder.Entity<User>(entity =>
{
entity.ToTable("usr_users");
entity.HasKey(u => u.Id).HasName("PK_usr_id");
});
Combo Key (like for user-role links):
modelBuilder.Entity<IdentityUserRole<string>>(entity =>
{
entity.ToTable("uro_user_roles");
entity.HasKey(ur => new { ur.UserId, ur.RoleId });
});
Extra Unique Rule:
modelBuilder.Entity<User>(entity =>
{
entity.ToTable("usr_users");
entity.HasAlternateKey(u => u.Email).HasName("AK_usr_email");
});
This makes Id the main key, ensures no duplicate UserId/RoleId pairs, and keeps Email unique—all with custom names for the database rules. Tags can do a basic
[Key]
, but anything trickier? You’re stuck without OnModelCreating.
Beyond Tables: Views and Secret Columns
Here’s where it gets wild: OnModelCreating isn’t just for regular tables. I can map other stuff too, like views or hidden columns, and it’s all in the same place.
Views Without Keys: Say I’ve got a database view that sums up user expenses. It’s not a table—it doesn’t have an ID—but I want to use it in my app:
modelBuilder.Entity<UserSummary>(entity =>
{
entity.HasNoKey();
entity.ToView("vw_user_summary");
entity.Property(u => u.UserName).HasColumnName("usr_username");
entity.Property(u => u.TotalExpenses).HasColumnName("total_expenses");
});
Now I can query UserSummary like any other data, no key required. That’s not even an option with tags.
Secret Columns: Sometimes I want a column in the database that’s not in my class—like who last edited something:
modelBuilder.Entity<Expense>(entity =>
{
entity.ToTable("exp_expenses");
entity.Property<string>("LastEditedBy")
.HasColumnName("exp_last_edited_by");
});
I can set or read LastEditedBy through EF Core’s backdoor methods, but my Expense class stays clean. Again, tags can’t touch this.
Taking Charge of Identity: Rewriting the Rules
If you’re using IdentityDbContext—like I do for user logins and roles—OnModelCreating gets even more powerful. Out of the box, it sets up tables with names like AspNetUsers and AspNetRoles. That’s fine if you like the defaults, but what if you want control? Maybe you hate the “AspNet” prefix and want usr_users instead. Here’s the kicker: OnModelCreating can override everything the base setup does—even ignore it completely if you’re feeling bold.
Normally, you’d call
base.OnModelCreating(modelBuilder)
to get the default Identity tables, then tweak them:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder); // Get the Identity defaults first
modelBuilder.Entity<User>(entity => entity.ToTable("usr_users"));
modelBuilder.Entity<Role>(entity => entity.ToTable("rol_roles"));
modelBuilder.Entity<IdentityUserRole<string>>(entity => entity.ToTable("uro_user_roles"));
}
This keeps the Identity magic—like user-role links—while letting me name tables my way. But here’s a big heads-up: if you’re altering base classes like IdentityDbContext, you must call
base.OnModelCreating(modelBuilder)
before you change those Identity tables. If you call it after—or skip it—your changes get ignored, and you’re stuck with AspNetUsers and friends. I learned that the hard way when my custom names wouldn’t stick—turns out the base call was stomping over them. Do it first, and you’re golden.
If I really wanted, I could skip
base.OnModelCreating
entirely and build it all from scratch. I’d have to set up every table, key, and relationship myself—users, roles, claims, the works. It’s a ton of work, but it proves the point: OnModelCreating gives me total freedom. I stick with the base call because it’s a time-saver, but knowing I can rewrite the rules—and how to make it stick—is power.
Wrapping It Up
So, yeah, I’m an OnModelCreating convert. It’s where I shape my columns, ditch properties I don’t need, set my keys, and even pull in views or hidden fields. It’s the final word—nothing overrules it, not some tag stuck on a class somewhere. For my IdentityDbContext with custom User and Role classes, it’s been a lifesaver—renaming tables, linking expenses, even overriding Identity’s defaults, all in one spot. I don’t need fancy words to explain it; I just need it to work. And it does.
If you’re still using those little tags, give OnModelCreating a shot. Start small—move a column name over, ignore a property, set a key. You’ll see what I mean. It’s not just a method—it’s your superpower for making EF Core bend to your will. And trust me, once you go this route, you won’t look back.
Got thoughts? Hit me up in the comments—I’d love to hear how you tame your database!
Miguel Bustamante
I design, you obey, code first.