0% found this document useful (0 votes)
30 views21 pages

Entity Framework Notes

Entity notes

Uploaded by

muddamsky
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
30 views21 pages

Entity Framework Notes

Entity notes

Uploaded by

muddamsky
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Entity Framework in C# - Comprehensive Study

Notes
Table of Contents
1. Introduction to Entity Framework
2. Entity Framework Architecture
3. Development Approaches
4. DbContext and DbSet
5. Data Configuration
6. Relationships and Navigation Properties
7. LINQ to Entities Queries
8. Loading Strategies
9. Migrations
10. Performance Optimization

1. Introduction to Entity Framework

What is Entity Framework?


Entity Framework (EF) is Microsoft's Object-Relational Mapping (ORM) framework for .NET
applications. It enables developers to work with relational data using domain-specific objects,
eliminating the need for most data-access code that developers usually need to write.
Key Features:
Object-Relational Mapping: Maps database tables to .NET objects
LINQ Support: Query databases using Language Integrated Query
Change Tracking: Automatically tracks changes to entities
Database Independence: Works with multiple database providers
Code-First Development: Generate database from code
Migration Support: Version control for database schema
Entity Framework vs Entity Framework Core
Feature EF 6.x EF Core

Platform .NET Framework only Cross-platform (.NET Core, .NET 5+)

Performance Mature but heavier Lightweight and faster

Features Feature-complete Rapidly evolving

Database Providers Limited Extensive support

Why Use Entity Framework?


1. Productivity: Reduces boilerplate data access code
2. Maintainability: Centralized data model configuration
3. Type Safety: Compile-time checking of queries
4. Database Agnostic: Switch between different database providers
5. Rich Feature Set: Built-in support for caching, validation, and transactions

2. Entity Framework Architecture

Core Components
Entity Classes (Domain Model)

public class Student


{
public int StudentId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime EnrollmentDate { get; set; }

// Navigation property
public virtual ICollection<Enrollment> Enrollments { get; set; }
}

DbContext
Central component for database interaction
Manages entity objects during runtime
Handles database connections and configurations
Provides change tracking capabilities
DbSet<TEntity>
Represents a collection of entities
Maps to database tables
Provides querying and modification operations
Database Providers
Translate EF Core operations to database-specific SQL
Handle database connections and command execution
Examples: SQL Server, MySQL, PostgreSQL, SQLite

Model Building Process


1. Conventions: Default rules for mapping entities to database schema
2. Data Annotations: Attributes applied to entity classes and properties
3. Fluent API: Configuration through method chaining in OnModelCreating
4. Model Creation: EF builds in-memory model representation
5. Query Translation: LINQ queries converted to SQL

3. Development Approaches

Code-First Approach
Create domain classes first, then generate database schema.
Advantages:
Full control over the code
Domain-driven design friendly
Version control friendly
Supports migrations
Workflow:
1. Create domain classes
2. Configure using Data Annotations or Fluent API
3. Generate migrations
4. Update database
Example:

public class Blog


{
public int BlogId { get; set; }
public string Title { get; set; }
public string Url { get; set; }
public List<Post> Posts { get; set; }
}
public class BlogContext : DbContext
{
public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)


{
optionsBuilder.UseSqlServer("connection_string");
}
}

Database-First Approach
Generate entity classes from existing database schema.
Advantages:
Quick start with existing databases
Automatically handles complex schemas
Good for legacy systems
Disadvantages:
Less control over generated code
Requires regeneration when schema changes
May not follow domain design principles
Command Example:

dotnet ef dbcontext scaffold "connection_string" Microsoft.EntityFrameworkCore.SqlServer

When to Use Each Approach


Use Code-First when:
Starting new projects
Following domain-driven design
Need full control over code structure
Working in agile environments
Use Database-First when:
Working with existing databases
Database design is already established
Working with complex legacy schemas
Database-centric development approach
4. DbContext and DbSet

DbContext Configuration
Basic Configuration:

public class ApplicationDbContext : DbContext


{
public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
: base(options)
{
}

public DbSet<Student> Students { get; set; }


public DbSet<Course> Courses { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)


{
if (!optionsBuilder.IsConfigured)
{
optionsBuilder.UseSqlServer("connection_string");
}
}

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
// Fluent API configurations
modelBuilder.Entity<Student>()
.Property(s => s.FirstName)
.IsRequired()
.HasMaxLength(50);
}
}

Dependency Injection in ASP.NET Core:

// Program.cs
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString));

DbContext Lifecycle
Scoped Lifetime (Recommended for Web Applications):
One instance per HTTP request
Automatically disposed after request completion
Change tracking works across the request
Key Methods:
SaveChanges(): Persists changes to database
SaveChangesAsync(): Asynchronous version
Entry(): Access to change tracking information
Database.EnsureCreated(): Create database if not exists

DbSet Operations
Querying:

// Get all students


var students = context.Students.ToList();

// Filtered query
var activeStudents = context.Students
.Where(s => s.IsActive)
.ToList();

Adding Entities:

var student = new Student { FirstName = "John", LastName = "Doe" };


context.Students.Add(student);
context.SaveChanges();

Updating Entities:

var student = context.Students.Find(1);


student.FirstName = "Jane";
context.SaveChanges();

Deleting Entities:

var student = context.Students.Find(1);


context.Students.Remove(student);
context.SaveChanges();

5. Data Configuration

Data Annotations
Data annotations provide a declarative way to configure entity properties using attributes.
Common Schema Attributes:

[Table("Students")]
public class Student
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int StudentId { get; set; }

[Column("student_name")]
[Required]
[MaxLength(100)]
public string Name { get; set; }

[Column(TypeName = "decimal(18,2)")]
public decimal GPA { get; set; }

[NotMapped]
public string FullName => $"{FirstName} {LastName}";
}

Validation Attributes:

public class Student


{
[Required(ErrorMessage = "First name is required")]
[StringLength(50, MinimumLength = 2)]
public string FirstName { get; set; }

[EmailAddress]
public string Email { get; set; }

[Range(18, 65)]
public int Age { get; set; }

[RegularExpression(@"^\d{10}$", ErrorMessage = "Phone must be 10 digits")]


public string Phone { get; set; }
}

Fluent API Configuration


Fluent API provides more comprehensive configuration options through method chaining.
Entity Configuration:

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
modelBuilder.Entity<Student>(entity =>
{
entity.ToTable("Students");
entity.HasKey(s => s.StudentId);

entity.Property(s => s.FirstName)


.IsRequired()
.HasMaxLength(50)
.HasColumnName("first_name");

entity.Property(s => s.Email)


.HasMaxLength(100);
entity.HasIndex(s => s.Email)
.IsUnique();
});
}

Property Configuration:

modelBuilder.Entity<Product>(entity =>
{
entity.Property(p => p.Price)
.HasColumnType("decimal(18,2)")
.HasDefaultValue(0);

entity.Property(p => p.CreatedDate)


.HasDefaultValueSql("GETDATE()");

entity.Property(p => p.Description)


.HasMaxLength(1000)
.IsUnicode(false);
});

Global Configuration:

protected override void OnModelCreating(ModelBuilder modelBuilder)


{
// Global filter
modelBuilder.Entity<Student>()
.HasQueryFilter(s => !s.IsDeleted);

// Default schema
modelBuilder.HasDefaultSchema("dbo");

// Conventions
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
entityType.SetTableName(entityType.DisplayName());
}
}

Entity Type Configuration Classes


For better organization, create separate configuration classes:

public class StudentConfiguration : IEntityTypeConfiguration<Student>


{
public void Configure(EntityTypeBuilder<Student> builder)
{
builder.ToTable("Students");

builder.HasKey(s => s.StudentId);


builder.Property(s => s.FirstName)
.IsRequired()
.HasMaxLength(50);

builder.Property(s => s.Email)


.HasMaxLength(100);

builder.HasIndex(s => s.Email)


.IsUnique();
}
}

// Apply in OnModelCreating
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.ApplyConfiguration(new StudentConfiguration());
// Or apply all configurations in assembly
modelBuilder.ApplyConfigurationsFromAssembly(Assembly.GetExecutingAssembly());
}

6. Relationships and Navigation Properties

Types of Relationships
One-to-Many Relationship:

public class Blog


{
public int BlogId { get; set; }
public string Title { get; set; }

// Navigation property
public ICollection<Post> Posts { get; set; }
}

public class Post


{
public int PostId { get; set; }
public string Title { get; set; }
public string Content { get; set; }

// Foreign key
public int BlogId { get; set; }
// Navigation property
public Blog Blog { get; set; }
}

One-to-One Relationship:

public class User


{
public int UserId { get; set; }
public string Username { get; set; }

// Navigation property
public UserProfile Profile { get; set; }
}

public class UserProfile


{
public int UserProfileId { get; set; }
public string FirstName { get; set; }
public string LastName { get; set; }

// Foreign key
public int UserId { get; set; }
// Navigation property
public User User { get; set; }
}

Many-to-Many Relationship (EF Core 5.0+):

public class Student


{
public int StudentId { get; set; }
public string Name { get; set; }

// Navigation property
public ICollection<Course> Courses { get; set; }
}

public class Course


{
public int CourseId { get; set; }
public string Title { get; set; }

// Navigation property
public ICollection<Student> Students { get; set; }
}

Configuring Relationships with Fluent API


One-to-Many Configuration:

modelBuilder.Entity<Post>()
.HasOne(p => p.Blog)
.WithMany(b => b.Posts)
.HasForeignKey(p => p.BlogId)
.OnDelete(DeleteBehavior.Cascade);

One-to-One Configuration:
modelBuilder.Entity<User>()
.HasOne(u => u.Profile)
.WithOne(p => p.User)
.HasForeignKey<UserProfile>(p => p.UserId);

Many-to-Many Configuration:

modelBuilder.Entity<Student>()
.HasMany(s => s.Courses)
.WithMany(c => c.Students)
.UsingEntity(j => j.ToTable("StudentCourses"));

Navigation Property Best Practices


1. Use ICollection<T> for collections instead of List<T>
2. Make navigation properties virtual for lazy loading
3. Initialize collections in constructors to avoid null reference exceptions
4. Use both sides of relationships for easier navigation

public class Blog


{
public Blog()
{
Posts = new HashSet<Post>();
}

public int BlogId { get; set; }


public string Title { get; set; }

public virtual ICollection<Post> Posts { get; set; }


}

7. LINQ to Entities Queries

Basic LINQ Queries


Method Syntax:

// Get all active students


var activeStudents = context.Students
.Where(s => s.IsActive)
.OrderBy(s => s.LastName)
.ToList();

// Get student by ID
var student = context.Students
.FirstOrDefault(s => s.StudentId == 1);
// Count students
var studentCount = context.Students
.Count(s => s.IsActive);

Query Syntax:

var activeStudents = from s in context.Students


where s.IsActive
orderby s.LastName
select s;

Advanced Queries
Projection:

var studentSummary = context.Students


.Select(s => new
{
s.StudentId,
FullName = s.FirstName + " " + s.LastName,
CourseCount = s.Enrollments.Count()
})
.ToList();

Grouping:

var studentsByGrade = context.Students


.GroupBy(s => s.Grade)
.Select(g => new
{
Grade = g.Key,
Count = g.Count(),
AverageGPA = g.Average(s => s.GPA)
})
.ToList();

Joins:

var studentCourses = context.Students


.Join(context.Enrollments,
s => s.StudentId,
e => e.StudentId,
(s, e) => new { Student = s, Enrollment = e })
.Join(context.Courses,
se => se.Enrollment.CourseId,
c => c.CourseId,
(se, c) => new
{
StudentName = se.Student.FirstName + " " + se.Student.LastName,
CourseName = c.Title
})
.ToList();

Asynchronous Queries

// Async methods for better performance


public async Task<List<Student>> GetActiveStudentsAsync()
{
return await context.Students
.Where(s => s.IsActive)
.ToListAsync();
}

public async Task<Student> GetStudentByIdAsync(int id)


{
return await context.Students
.FirstOrDefaultAsync(s => s.StudentId == id);
}

Raw SQL Queries

// Execute raw SQL


var students = context.Students
.FromSqlRaw("SELECT * FROM Students WHERE IsActive = 1")
.ToList();

// Parameterized queries
var studentId = 1;
var student = context.Students
.FromSqlInterpolated($"SELECT * FROM Students WHERE StudentId = {studentId}")
.FirstOrDefault();

8. Loading Strategies

Lazy Loading
Loads related data automatically when navigation properties are accessed.
Configuration:

// Enable lazy loading


protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseLazyLoadingProxies();
}

// Make navigation properties virtual


public class Blog
{
public int BlogId { get; set; }
public string Title { get; set; }

public virtual ICollection<Post> Posts { get; set; }


}

Usage:

var blog = context.Blogs.First();


// Posts are loaded automatically when accessed
var postCount = blog.Posts.Count(); // Triggers lazy loading

Advantages:
Transparent and easy to use
Loads data only when needed
Reduces initial query complexity
Disadvantages:
Can cause N+1 query problems
Requires active DbContext
May cause unexpected database calls

Eager Loading
Loads related data upfront using Include() method.
Basic Eager Loading:

var blogs = context.Blogs


.Include(b => b.Posts)
.ToList();

Multiple Levels:

var blogs = context.Blogs


.Include(b => b.Posts)
.ThenInclude(p => p.Comments)
.Include(b => b.Author)
.ToList();

Conditional Include:

var blogs = context.Blogs


.Include(b => b.Posts.Where(p => p.IsPublished))
.ToList();

Advantages:
Predictable performance
Single database query
No N+1 problems
Disadvantages:
May load unnecessary data
Complex queries with joins
Increased memory usage

Explicit Loading
Manually load related data when needed.
Loading Collections:

var blog = context.Blogs.First();

context.Entry(blog)
.Collection(b => b.Posts)
.Load();

Loading References:

var post = context.Posts.First();

context.Entry(post)
.Reference(p => p.Blog)
.Load();

Querying Related Data:

var publishedPostsCount = context.Entry(blog)


.Collection(b => b.Posts)
.Query()
.Where(p => p.IsPublished)
.Count();
Split Queries (EF Core 5.0+)
Splits complex queries into multiple simpler queries.

var blogs = context.Blogs


.Include(b => b.Posts)
.Include(b => b.Tags)
.AsSplitQuery()
.ToList();

Loading Strategy Recommendations


Use Eager Loading when you know you'll need related data
Use Lazy Loading for simple scenarios with careful N+1 monitoring
Use Explicit Loading for fine-grained control
Use Split Queries for complex includes with multiple collections

9. Migrations

What are Migrations?


Migrations provide a way to incrementally update the database schema to keep it in sync with
the application's data model while preserving existing data.

Migration Commands
Add Migration:

# .NET CLI
dotnet ef migrations add InitialCreate

# Package Manager Console


Add-Migration InitialCreate

Update Database:

# .NET CLI
dotnet ef database update

# Package Manager Console


Update-Database

Remove Migration:

# .NET CLI
dotnet ef migrations remove
# Package Manager Console
Remove-Migration

Migration Structure

public partial class InitialCreate : Migration


{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Students",
columns: table => new
{
StudentId = table.Column<int>(nullable: false)
.Annotation("SqlServer:Identity", "1, 1"),
FirstName = table.Column<string>(maxLength: 50, nullable: false),
LastName = table.Column<string>(maxLength: 50, nullable: false),
Email = table.Column<string>(maxLength: 100, nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Students", x => x.StudentId);
});
}

protected override void Down(MigrationBuilder migrationBuilder)


{
migrationBuilder.DropTable(name: "Students");
}
}

Advanced Migration Scenarios


Data Seeding:

protected override void Up(MigrationBuilder migrationBuilder)


{
migrationBuilder.InsertData(
table: "Students",
columns: new[] { "FirstName", "LastName", "Email" },
values: new object[,]
{
{ "John", "Doe", "[email protected]" },
{ "Jane", "Smith", "[email protected]" }
});
}

Custom Migration Operations:


protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql("CREATE INDEX IX_Students_LastName ON Students (LastName)");

migrationBuilder.Sql(@"
CREATE TRIGGER tr_Students_Audit
ON Students
AFTER UPDATE
AS BEGIN
-- Audit logic here
END
");
}

Migration Best Practices


1. Review generated migrations before applying
2. Backup database before major migrations
3. Test migrations in development environment
4. Use descriptive migration names
5. Handle data migration separately for complex changes
6. Consider downtime for production deployments

Production Deployment
Generate SQL Scripts:

dotnet ef migrations script --from PreviousMigration --to TargetMigration

Conditional Migrations:

if (context.Database.GetPendingMigrations().Any())
{
context.Database.Migrate();
}

10. Performance Optimization

Query Optimization Techniques


1. Use AsNoTracking() for Read-Only Queries:

var students = context.Students


.AsNoTracking()
.Where(s => s.IsActive)
.ToList();

2. Select Only Required Columns:

var studentNames = context.Students


.Select(s => new { s.StudentId, s.FirstName, s.LastName })
.ToList();

3. Use Pagination:

var students = context.Students


.OrderBy(s => s.LastName)
.Skip(pageIndex * pageSize)
.Take(pageSize)
.ToList();

4. Compile Frequently Used Queries:

private static readonly Func<SchoolContext, bool, IEnumerable<Student>>


GetActiveStudents = EF.CompileQuery(
(SchoolContext context, bool isActive) =>
context.Students.Where(s => s.IsActive == isActive));

Bulk Operations
Batch Operations:

// Adding multiple entities


var students = new List<Student>
{
new Student { FirstName = "John", LastName = "Doe" },
new Student { FirstName = "Jane", LastName = "Smith" }
};

context.Students.AddRange(students);
context.SaveChanges();

Bulk Updates with ExecuteUpdate (EF Core 7.0+):

context.Students
.Where(s => s.Grade == "A")
.ExecuteUpdate(s => s.SetProperty(p => p.IsHonorRoll, true));
Indexing Considerations
Configure Indexes:

modelBuilder.Entity<Student>()
.HasIndex(s => s.Email)
.IsUnique();

modelBuilder.Entity<Student>()
.HasIndex(s => new { s.LastName, s.FirstName })
.HasDatabaseName("IX_Student_Name");

Connection Management
Connection Pooling:

services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(connectionString)
.EnableServiceProviderCaching()
.EnableSensitiveDataLogging(isDevelopment));

Monitoring and Profiling


Enable Logging:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)


{
optionsBuilder
.UseSqlServer(connectionString)
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging();
}

Performance Best Practices


1. Use appropriate loading strategies based on usage patterns
2. Optimize database schema with proper indexes
3. Monitor query execution plans
4. Use connection pooling
5. Implement caching for frequently accessed data
6. Avoid N+1 query problems
7. Use async methods for I/O operations
8. Profile and measure performance regularly
Common Performance Anti-Patterns
Avoid:
Using ToList() unnecessarily in LINQ chains
Loading entire entities when only few properties needed
Not using indexes for frequently queried columns
Performing queries in loops (N+1 problem)
Not disposing DbContext properly
Using synchronous methods in async contexts

Conclusion
Entity Framework provides a powerful and flexible way to work with databases in .NET
applications. Understanding its core concepts, configuration options, and performance
considerations is crucial for building efficient data-driven applications.
Key Takeaways:
Choose the appropriate development approach (Code-First vs Database-First)
Configure entities using Data Annotations or Fluent API
Understand different loading strategies and their trade-offs
Use migrations for database schema versioning
Apply performance optimization techniques
Follow best practices for production applications
Next Steps:
Practice with different relationship configurations
Experiment with advanced querying techniques
Learn about EF Core specific features
Explore database-specific optimizations
Study real-world performance scenarios
This comprehensive guide covers the essential aspects of Entity Framework that every .NET
developer should master for effective database programming.

You might also like