Entity Framework: Advanced Topics

This section delves into more complex scenarios and features within Entity Framework, empowering developers to build robust and efficient data access solutions.

1. Customizing Entity Framework Behavior

1.1 Customizing the DbContext

Learn how to extend the DbContext class to add custom methods, override default behavior, and manage complex initialization logic.


public class MyCustomDbContext : DbContext
{
    public MyCustomDbContext(string connectionString) : base(connectionString) {}

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

    public override int SaveChanges()
    {
        // Custom logic before saving changes
        Console.WriteLine("Before SaveChanges...");
        var result = base.SaveChanges();
        // Custom logic after saving changes
        Console.WriteLine("After SaveChanges...");
        return result;
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        // Further model configuration
        modelBuilder.Entity<Product>().Property(p => p.Name).IsRequired();
        base.OnModelCreating(modelBuilder);
    }
}
        

1.2 Customizing Conventions

Understand how to define and apply custom conventions to automate the configuration of your entity models, reducing repetitive code.


public class LowercaseTableNameConvention : IConvention
{
    public void Apply(ConventionBuilder builder)
    {
        builder.Properties().Where(p => p.DeclaringType.IsComplexType == false)
               .Configure(c => c.ToColumn(c.Name.ToLower()));

        builder.Types().Where(t => t.ClrType.IsClass && !t.ClrType.IsAbstract && t.ClrType.Namespace != null && t.ClrType.Namespace.StartsWith("MyProject.Models"))
               .Configure(c => c.ToTable(c.Name.ToLower()));
    }
}

// In DbContext:
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
    modelBuilder.Conventions.Add(new LowercaseTableNameConvention());
    base.OnModelCreating(modelBuilder);
}
        

2. Performance Optimization Techniques

2.1 Lazy Loading vs. Eager Loading

Explore the trade-offs between lazy loading and eager loading, and learn when to use each for optimal performance.


// Eager Loading Example:
var ordersWithCustomers = _context.Orders
    .Include(o => o.Customer)
    .Include(o => o.OrderItems)
        .ThenInclude(oi => oi.Product);

// Lazy Loading (if enabled and virtual navigation properties are used)
var order = _context.Orders.Find(1);
var customerName = order.Customer.Name; // Customer is loaded here if lazy loading is enabled
        

2.2 Query Caching

Understand how Entity Framework caches query results and how to leverage it to improve performance for frequently executed queries.

2.3 Compiled Queries

Discover how compiled queries can provide significant performance benefits by pre-compiling the LINQ to Entities query into an efficient execution plan.


public static class ProductQueries
{
    public static readonly Func<MyCustomDbContext, int, Product> GetProductById =
        CompiledQuery.Compile((MyCustomDbContext context, int productId) =>
            context.Products.FirstOrDefault(p => p.Id == productId));
}

// Usage:
var product = ProductQueries.GetProductById(_context, 123);
        

3. Advanced Mapping Scenarios

3.1 Table-Per-Type (TPT) and Table-Per-Concrete-Type (TPCTC)

Learn how to map inheritance hierarchies using TPT and TPCTC strategies, where different tables represent derived types.

3.2 Table-Per-Hierarchy (TPH)

Understand the TPH strategy, where a single table stores all entities in an inheritance hierarchy, differentiated by a discriminator column.

3.3 Entity Splitting

Explore entity splitting, where properties from a single entity are mapped to multiple database tables.

4. Handling Concurrency

4.1 Optimistic Concurrency

Implement optimistic concurrency control using row versioning or timestamp columns to detect and handle concurrent modifications.


public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }

    [Timestamp] // Or [ConcurrencyCheck] attribute for other types
    public byte[] RowVersion { get; set; }
}

// In DbContext SaveChanges:
try
{
    _context.SaveChanges();
}
catch (DbUpdateConcurrencyException ex)
{
    // Handle concurrency conflict, e.g., inform user or resolve
    var entry = ex.Entries.Single();
    var databaseValues = (Product)entry.GetDatabaseValues().ToObject();
    var proposedValues = (Product)entry.CurrentValues.ToObject();

    // Example: Reload original values from database
    entry.OriginalValues.SetValues(databaseValues);
    // Inform the user about the conflict and allow them to re-apply changes.
}
        

5. Interacting with Stored Procedures and Raw SQL

5.1 Executing Stored Procedures

Learn how to call stored procedures from Entity Framework and map their results to your entities.


var products = _context.Database.SqlQuery<Product>("EXEC GetProductsByCategory @CategoryId",
    new SqlParameter("@CategoryId", categoryId)).ToList();
        

5.2 Executing Raw SQL Queries

Understand how to execute custom SQL queries when LINQ to Entities is not sufficient or for performance tuning.


var products = _context.Products.SqlQuery("SELECT * FROM dbo.Products WHERE Price > {0}", 50.0m).ToList();
        
Tip: While raw SQL can be powerful, favor LINQ to Entities whenever possible for better maintainability and type safety.

6. Asynchronous Operations

Implement asynchronous data access operations using async/await patterns for improved responsiveness in applications.


public async Task<Product> GetProductByIdAsync(int productId)
{
    return await _context.Products.FindAsync(productId);
}

public async Task<List<Product>> GetAllProductsAsync()
{
    return await _context.Products.ToListAsync();
}
        
Note: Ensure your application project targets a framework version that supports asynchronous operations.

7. Entity Framework Core vs. Entity Framework 6

Understand the key differences and migration paths between Entity Framework 6 and the newer Entity Framework Core, which is designed for .NET Core and .NET 5+.