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:

EF Core can often infer these relationships automatically, but explicit configuration is recommended for clarity and robustness.

Key Concepts

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.