In any robust ASP.NET Core MVC application, three pillars are crucial for long-term stability and scalability:
-
Entity Relationships (One-to-One, One-to-Many, Many-to-Many)
-
Database Migrations
-
Performance Optimization
1. Defining and Managing Relationships
One-to-Many Relationship
public class Customer
{
public int CustomerId { get; set; }
public string Name { get; set; }
// Navigation Property
public ICollection<Order> Orders { get; set; }
}
public class Order
{
public int OrderId { get; set; }
public string Product { get; set; }
// Foreign Key
public int CustomerId { get; set; }
public Customer Customer { get; set; }
}
Fluent API Configuration (optional)
modelBuilder.Entity<Order>()
.HasOne(o => o.Customer)
.WithMany(c => c.Orders)
.HasForeignKey(o => o.CustomerId);
One-to-One Relationship
public class User
{
public int UserId { get; set; }
public string Username { get; set; }
public UserProfile Profile { get; set; }
}
public class UserProfile
{
[Key]
public int UserId { get; set; }
public string Bio { get; set; }
public User User { get; set; }
}
With fluent API
modelBuilder.Entity<User>()
.HasOne(u => u.Profile)
.WithOne(p => p.User)
.HasForeignKey<UserProfile>(p => p.UserId);
Many-to-Many Relationship (EF Core 5+)
public class Student
{
public int StudentId { get; set; }
public string Name { get; set; }
public ICollection<Course> Courses { get; set; }
}
public class Course
{
public int CourseId { get; set; }
public string Title { get; set; }
public ICollection<Student> Students { get; set; }
}
2. Migrations and Database Management
Adding a Migration
dotnet ef migrations add InitialCreate
Updating the Database
dotnet ef database update
Managing Migrations in Teams
-
Keep migrations in version control (e.g., Git).
-
Avoid auto-generating in production environments.
-
Prefer code-first for fast prototyping and database-first for legacy systems.
Reverting a Migration
dotnet ef migrations remove
Or roll back
dotnet ef database update PreviousMigrationName
3. Performance Optimization
3.1 Use AsNoTracking
for Read-Only Queries
var users = await _context.Users
.AsNoTracking()
.ToListAsync();
3.2 Apply Indexes
[Index(nameof(Email), IsUnique = true)]
public class User
{
public int Id { get; set; }
public string Email { get; set; }
}
Or via Fluent API
modelBuilder.Entity<User>()
.HasIndex(u => u.Email)
.IsUnique();
3.3 Use Eager Loading Wisely
var orders = await _context.Orders
.Include(o => o.Customer)
.ToListAsync();
Avoid N+1 queries, but don't overfetch. Use Select
to project minimal data when possible.
3.4 Pagination with Skip()
and Take()
var pagedUsers = await _context.Users
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
3.5 Caching (MemoryCache or Distributed Cache)
services.AddMemoryCache();
public class MyService
{
private readonly IMemoryCache _cache;
public MyService(IMemoryCache cache)
{
_cache = cache;
}
public async Task<List<User>> GetCachedUsersAsync()
{
return await _cache.GetOrCreateAsync("user_list", async entry =>
{
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(10);
return await _context.Users.ToListAsync();
});
}
}
3.6 Connection Pooling and Compression
"ConnectionStrings": {
"DefaultConnection": "Server=.;Database=MyAppDb;Trusted_Connection=True;MultipleActiveResultSets=true;TrustServerCertificate=True;"
}
Note. Please download the project from the top of this article.
Conclusion
Properly managing relationships, database migrations, and performance can make or break an ASP.NET Core MVC application at scale. By leveraging EF Core's powerful relationship handling, automating schema changes with migrations, and implementing performance best practices, developers can build fast, maintainable, and scalable apps.