Entity Framework Core: Models and Data Access
This section delves into how to define models (entities) and interact with your database using Entity Framework Core (EF Core). EF Core provides a powerful object-relational mapper (ORM) that simplifies data access in .NET applications.
Defining Entity Models
Your application's data is represented by Plain Old CLR Objects (POCOs) that EF Core maps to database tables. These are often referred to as entities.
A simple entity class might look like this:
public class Product
{
public int ProductId { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
public int CategoryId { get; set; }
public Category Category { get; set; }
}
public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }
public ICollection<Product> Products { get; set; }
}
EF Core conventions will typically infer the primary key (e.g., ProductId
, CategoryId
) and relationships based on property names.
Configuring Models
While conventions cover many scenarios, you can explicitly configure your models using the DbContext
and the Fluent API or Data Annotations.
Fluent API Configuration
This is done within the `OnModelCreating` method of your DbContext
:
using Microsoft.EntityFrameworkCore;
public class ApplicationDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options) { }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.HasKey(p => p.ProductId);
modelBuilder.Entity<Product>()
.Property(p => p.Name)
.IsRequired()
.HasMaxLength(100);
modelBuilder.Entity<Product>()
.HasOne(p => p.Category)
.WithMany(c => c.Products)
.HasForeignKey(p => p.CategoryId);
}
}
Data Annotations
Alternatively, you can use attributes directly on your entity classes:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
public class Product
{
[Key]
public int ProductId { get; set; }
[Required]
[MaxLength(100)]
public string Name { get; set; }
public decimal Price { get; set; }
[ForeignKey("Category")]
public int CategoryId { get; set; }
public Category Category { get; set; }
}
public class Category
{
[Key]
public int CategoryId { get; set; }
[Required]
public string Name { get; set; }
public ICollection<Product> Products { get; set; }
}
Performing Data Access Operations
Once your models and DbContext
are set up, you can perform CRUD (Create, Read, Update, Delete) operations.
Querying Data (Read)
You can use LINQ to query your entities:
using (var context = new ApplicationDbContext(/* options */))
{
// Get all products
var allProducts = await context.Products.ToListAsync();
// Get products in a specific category
var categoryId = 1;
var productsInCategory = await context.Products
.Where(p => p.CategoryId == categoryId)
.ToListAsync();
// Include related data (e.g., Category)
var productsWithCategories = await context.Products
.Include(p => p.Category)
.ToListAsync();
}
Adding Data (Create)
using (var context = new ApplicationDbContext(/* options */))
{
var newCategory = new Category { Name = "Electronics" };
context.Categories.Add(newCategory);
await context.SaveChangesAsync(); // Saves the category and returns its ID
var newProduct = new Product
{
Name = "Laptop",
Price = 1200.00m,
CategoryId = newCategory.CategoryId // Use the ID from the saved category
};
context.Products.Add(newProduct);
await context.SaveChangesAsync();
}
Updating Data (Update)
using (var context = new ApplicationDbContext(/* options */))
{
var productToUpdate = await context.Products.FindAsync(1); // Assuming product with ID 1 exists
if (productToUpdate != null)
{
productToUpdate.Price = 1250.00m;
await context.SaveChangesAsync();
}
}
Deleting Data (Delete)
using (var context = new ApplicationDbContext(/* options */))
{
var productToDelete = await context.Products.FindAsync(2); // Assuming product with ID 2 exists
if (productToDelete != null)
{
context.Products.Remove(productToDelete);
await context.SaveChangesAsync();
}
}
Tip: Use .AsNoTracking()
on queries when you only need to read data and don't intend to modify and save it. This can improve performance by reducing overhead.
Relationships
EF Core supports various relationship types:
- One-to-One: E.g., a
User
and theirUserProfile
. - One-to-Many: E.g., a
Category
can have manyProducts
. - Many-to-Many: E.g.,
Posts
can have manyTags
, andTags
can be applied to manyPosts
. This typically requires a joining table.
EF Core can often infer these relationships automatically, but explicit configuration is recommended for clarity and robustness.
Key Concepts
DbContext
: The primary class for interacting with the database. It represents a session with the database and allows you to query and save data.DbSet<TEntity>
: Represents a collection of all entities in the store for a given type. You accessDbSet
properties from yourDbContext
instance.- Migrations: A feature that allows you to evolve your database schema over time as your application models change.
Understanding how to model your data and perform efficient data access operations is fundamental to building robust applications with Entity Framework Core.
Continue to the next section to learn about Entity Framework Core Migrations.