Entity Framework Core Advanced Topics

Introduction

This section covers advanced concepts and patterns for using Entity Framework Core (EF Core) effectively in complex scenarios. We'll delve into topics such as change tracking customization, performance tuning, and advanced querying techniques.

Customizing Change Tracking

EF Core's change tracking mechanism is powerful, but sometimes you need more control. You can customize how EF Core detects changes to your entities.

Value Comparers

Value comparers allow you to define custom logic for comparing property values, which is particularly useful for custom types or when you need to ignore certain aspects of a value for change tracking purposes.


modelBuilder.Entity<Product>()
    .Property(p => p.Color)
    .HasConversion<string>()
    .Metadata.SetValueComparer(new MyCustomColorComparer());

public class MyCustomColorComparer : ValueComparer<Color>
{
    public MyCustomColorComparer() : base(
        (c1, c2) => (c1 == null && c2 == null) || (c1 != null && c2 != null && c1.Equals(c2)),
        c => c.GetHashCode())
    {
    }
}
            

Property-Based Change Tracking

You can configure EF Core to track changes only for specific properties of an entity.


modelBuilder.Entity<Order>()
    .Property(o => o.OrderDate)
    .IsConcurrencyToken(); // Enables optimistic concurrency for OrderDate
            

Advanced Querying

Projection with `Select`

Leveraging the `Select` method to project your queries into anonymous types or specific DTOs can significantly improve performance by fetching only the necessary data.


var orderSummaries = await _context.Orders
    .Where(o => o.CustomerId == customerId)
    .Select(o => new {
        OrderId = o.Id,
        OrderDate = o.OrderDate,
        TotalAmount = o.OrderItems.Sum(oi => oi.Quantity * oi.UnitPrice)
    })
    .ToListAsync();
            

Complex Joins and Subqueries

EF Core can translate complex LINQ queries involving multiple joins and subqueries into efficient SQL.


var customersWithRecentOrders = await _context.Customers
    .Where(c => c.Orders.Any(o => o.OrderDate > DateTime.Today.AddMonths(-6)))
    .Select(c => new {
        c.Name,
        RecentOrderCount = c.Orders.Count(o => o.OrderDate > DateTime.Today.AddMonths(-6))
    })
    .ToListAsync();
            

Concurrency Handling

EF Core supports various concurrency control strategies to manage simultaneous updates to the same data.

Optimistic Concurrency

Optimistic concurrency assumes that conflicts are rare. It typically involves adding a version or timestamp column to your entity. When saving changes, EF Core checks if the version has changed since the entity was read. If it has, a DbUpdateConcurrencyException is thrown.

Note: To implement optimistic concurrency, add a property (e.g., [Timestamp] or [ConcurrencyCheck] attribute) to your entity and configure it in the DbContext.

Pessimistic Concurrency

Pessimistic concurrency assumes conflicts are common and locks resources to prevent others from modifying them until the transaction is complete. This is usually handled at the database level.

Raw SQL Queries

In scenarios where LINQ might not be sufficient or for performance-critical operations, you can execute raw SQL queries.

Executing Raw SQL Queries


var rawSqlResult = await _context.Products
    .FromSqlRaw("SELECT * FROM Products WHERE Category = {0}", "Electronics")
    .ToListAsync();
            

Executing Raw SQL Commands

For operations that don't return entities, like updates or deletes via SQL.


var rowsAffected = await _context.Database
    .ExecuteSqlRawAsync("UPDATE Products SET Price = Price * 1.1 WHERE Category = {0}", "Books");
            

Advanced Configuration

Lazy Loading

Lazy loading allows related entities to be loaded automatically when a navigation property is accessed for the first time. This requires the entity types to be reference types and the navigation properties to be virtual.

Tip: Enable lazy loading by adding the Microsoft.EntityFrameworkCore.Proxies NuGet package and calling UseLazyLoadingProxies() on your DbContextOptionsBuilder.

Eager Loading

Eager loading explicitly loads related entities along with the main entities using `Include()` or `ThenInclude()`. This is generally preferred over lazy loading for performance reasons in most scenarios.

Explicit Loading

Explicit loading allows you to load related entities on demand after the primary entity has already been loaded.


var product = await _context.Products.FindAsync(productId);
if (product != null)
{
    await _context.Entry(product)
        .Reference(p => p.Category)
        .LoadAsync();
}
            

Performance Tuning Considerations

Conclusion

Mastering these advanced EF Core topics will empower you to build more robust, performant, and scalable data-driven applications. Remember to profile your application and choose the patterns that best suit your specific needs.