NANDHOO.

Entity Framework Core (EF Core)

Entity Framework Core (EF Core)


Entity Framework (EF) Core is a modern Object-Relational Mapper (ORM) for .NET. It allows developers to work with a database using C# objects, eliminating the need to write most of the data-access code that developers usually need to write.




1. The Core: DbContext & DbSet


The DbContext is the primary class that coordinates Entity Framework functionality for a given data model. It represents a session with the database and allows you to query and save instances of your entities.


  • DbSet<TEntity>: Represents a table in the database. You use it to perform CRUD (Create, Read, Update, Delete) operations.

public class AppDbContext : DbContext
{
    public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }

public DbSet<Product> Products { get; set; }
public DbSet<Category> Categories { get; set; }

}




2. Configuration: Data Annotations vs. Fluent API


There are two ways to configure how your C# classes map to database tables.


A. Data Annotations (Attributes)

Simple and easy to read. You apply attributes directly to your class properties.

  • [Key]: Defines the Primary Key.
  • [Required]: Makes a column NOT NULL.
  • [StringLength(100)]: Sets the maximum character limit.
  • [ForeignKey("Name")]: Explicitly defines a foreign key relationship.
  • [Table("MyTableName")]: Maps the class to a specific table name.

B. Fluent API (OnModelCreating)

More powerful and keeps your entity classes "clean" of database-specific attributes. It is defined inside your DbContext.


protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Product>()
        .Property(p => p.Name)
        .IsRequired()
        .HasMaxLength(200);

modelBuilder.Entity<Product>()
    .HasIndex(p => p.Sku)
    .IsUnique();

}




3. Mastering Relationships


One-to-Many (The Standard)

A single Category can have many Products.

public class Category {
    public int Id { get; set; }
    public List<Product> Products { get; set; } = new();
}

public class Product { public int Id { get; set; } public string Name { get; set; } public int CategoryId { get; set; } // Foreign Key public Category Category { get; set; } // Navigation Property }


One-to-One

A User has exactly one Profile.

public class User {
    public int Id { get; set; }
    public UserProfile Profile { get; set; }
}

public class UserProfile { public int Id { get; set; } public int UserId { get; set; } // Foreign Key public User User { get; set; } }


Many-to-Many

A Student can enroll in many Courses, and a Course can have many Students. In EF Core 5.0+, this is handled automatically with a "shadow" join table.

public class Student {
    public int Id { get; set; }
    public ICollection<Course> Courses { get; set; }
}

public class Course { public int Id { get; set; } public ICollection Students { get; set; } }




4. Database Migrations


Migrations allow you to evolve your database schema as your C# models change, without losing data.


The Lifecycle:

  1. Modify Models: Add a property or a new class.
  2. Add Migration: Generate the C# code that describes the change.
    dotnet ef migrations add InitialCreate
    
  3. Update Database: Apply the changes to the actual SQL database.
    dotnet ef database update
    



5. Eager Loading (Include)


By default, EF Core does not load related data (to save performance). To load a Product along with its Category, you must use the .Include() method.


var products = context.Products
    .Include(p => p.Category)
    .Where(p => p.Price > 100)
    .ToList();



6. Performance Best Practices


  1. AsNoTracking(): Use this for read-only queries. It tells EF Core not to waste memory tracking changes for those objects.
  2. AnyAsync(): Use this to check for existence instead of .Count() > 0 for better performance.
  3. Filtered Includes: In modern EF Core, you can filter the data inside an .Include() call.