Section 2 of 7
DbContext and Entities
🎯 What You'll Learn
- What is DbContext
- Creating entity classes
- Configuring DbContext
- Data annotations
- Fluent API configuration
- Dependency injection setup
What is DbContext?
DbContext is the primary class for interacting with the database. It:
- Represents a session with the database
- Manages entity instances and their state
- Provides DbSet properties for querying
- Tracks changes to entities
- Saves changes to the database
Creating Entities
Entities are C# classes that represent database tables.
Models/Product.cs
C#
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public string? Description { get; set; }
public int Quantity { get; set; }
public DateTime CreatedAt { get; set; }
}
Conventions
EF Core uses conventions to infer database schema:
| Convention | Result |
|---|---|
| Class name | Table name (Products) |
Property named Id or ProductId |
Primary key |
string property |
nvarchar(max) column |
int, long primary key |
Identity column (auto-increment) |
Nullable type (int?) |
Nullable column |
Creating DbContext
Data/InvenTrackDbContext.cs
C#
public class InvenTrackDbContext : DbContext
{
// DbSet properties represent tables
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
public DbSet<Order> Orders { get; set; }
public InvenTrackDbContext(DbContextOptions<InvenTrackDbContext> options)
: base(options)
{
}
}
Configuring DbContext
In Program.cs (Recommended)
Program.cs
C#
var builder = WebApplication.CreateBuilder(args);
// Register DbContext with DI
builder.Services.AddDbContext<InvenTrackDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();
app.Run();
Connection String in appsettings.json
appsettings.json
JSON
{
"ConnectionStrings": {
"DefaultConnection": "Server=.;Database=InvenTrack;Trusted_Connection=True;TrustServerCertificate=True;"
}
}
Data Annotations
Data Annotations configure entities using attributes.
Using Data Annotations
C#
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
[Table("Products")]
public class Product
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; } = string.Empty;
[Column(TypeName = "decimal(18,2)")]
public decimal Price { get; set; }
[MaxLength(500)]
public string? Description { get; set; }
public int Quantity { get; set; }
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
Common Data Annotations
| Annotation | Purpose |
|---|---|
[Key] |
Mark as primary key |
[Required] |
Not null column |
[MaxLength(100)] |
String length limit |
[Column("ProductName")] |
Custom column name |
[Table("Products")] |
Custom table name |
[ForeignKey("CategoryId")] |
Foreign key |
[NotMapped] |
Exclude from database |
Fluent API Configuration
Fluent API provides more control than data annotations.
OnModelCreating
C#
public class InvenTrackDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public InvenTrackDbContext(DbContextOptions<InvenTrackDbContext> options)
: base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>(entity =>
{
// Table name
entity.ToTable("Products");
// Primary key
entity.HasKey(p => p.Id);
// Properties
entity.Property(p => p.Name)
.IsRequired()
.HasMaxLength(100);
entity.Property(p => p.Price)
.HasColumnType("decimal(18,2)");
entity.Property(p => p.Description)
.HasMaxLength(500);
entity.Property(p => p.CreatedAt)
.HasDefaultValueSql("GETUTCDATE()");
// Index
entity.HasIndex(p => p.Name);
});
}
}
Fluent API vs Data Annotations
| Aspect | Fluent API | Data Annotations |
|---|---|---|
| Power | More features | Limited features |
| Readability | Centralized | On entity classes |
| Separation | Clean entities | Attributes on entities |
| Precedence | Overrides annotations | Lower priority |
Using DbContext
In Controllers
Controllers/ProductsController.cs
C#
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly InvenTrackDbContext _context;
public ProductsController(InvenTrackDbContext context)
{
_context = context;
}
[HttpGet]
public async Task<ActionResult> GetProducts()
{
var products = await _context.Products.ToListAsync();
return Ok(products);
}
[HttpPost]
public async Task<ActionResult> CreateProduct(Product product)
{
_context.Products.Add(product);
await _context.SaveChangesAsync();
return CreatedAtAction(nameof(GetProduct), new { id = product.Id }, product);
}
}
Complete InvenTrack Example
Models/Product.cs
C#
public class Product
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
public decimal Price { get; set; }
public string? Description { get; set; }
public int Quantity { get; set; }
public int CategoryId { get; set; }
public DateTime CreatedAt { get; set; }
}
public class Category
{
public int Id { get; set; }
public string Name { get; set; } = string.Empty;
}
Data/InvenTrackDbContext.cs
C#
public class InvenTrackDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
public InvenTrackDbContext(DbContextOptions<InvenTrackDbContext> options)
: base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>(entity =>
{
entity.Property(p => p.Name).HasMaxLength(100);
entity.Property(p => p.Price).HasColumnType("decimal(18,2)");
entity.Property(p => p.CreatedAt).HasDefaultValueSql("GETUTCDATE()");
});
modelBuilder.Entity<Category>(entity =>
{
entity.Property(c => c.Name).HasMaxLength(50);
});
}
}
Program.cs
C#
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
builder.Services.AddDbContext<InvenTrackDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));
var app = builder.Build();
app.MapControllers();
app.Run();
Best Practices
- Use DI: Register DbContext with dependency injection
- Connection strings: Store in appsettings.json
- Async operations: Always use async methods
- Fluent API: Prefer for complex configurations
- Data annotations: Use for simple validation
- DbSet properties: Name as plural (Products, not Product)
- Dispose: DI handles disposal automatically
Key Takeaways
- DbContext: Central class for database operations
- Entities: C# classes that map to tables
- DbSet: Properties representing tables
- Conventions: EF Core infers schema from code
- Data Annotations: Attributes for configuration
- Fluent API: More powerful configuration in OnModelCreating
- DI: Register with AddDbContext
- Connection strings: Configure in appsettings.json
🎯 Next Steps
You now understand DbContext and entities! In the next section, we'll explore Code-First Migrations—how to create and manage your database schema from code.