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.
- Lazy Loading: Related entities are loaded only when they are accessed. Can lead to the "N+1 query problem".
- Eager Loading: Related entities are loaded along with the primary entities in a single query. Use
Include()
andThenInclude()
.
// 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();
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();
}
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+.
- EF Core is cross-platform and offers improved performance.
- EF6 is the legacy version for .NET Framework.