Entity Framework: Data Mapping, Query Execution, and State Management Techniques
Data Annotation Attributes for Schema Configuraton
[Table("Products")]
public class Product
{
[Key]
[Column("ProductKey")]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ProductKey { get; set; }
[Required]
[StringLength(100)]
public string ProductName { get; set; }
[Column(TypeName = "varchar")]
[StringLength(50)]
public string SKU { get; set; }
[ForeignKey("Category")]
public int CategoryId { get; set; }
public virtual Category Category { get; set; }
public decimal? UnitPrice { get; set; }
[NotMapped]
public string DisplayPrice { get; set; }
}
The [DatabaseGenerated] attribute supports three modes: None for manual assignment, Identity for auto-increment columns, and Computed for database-calculated values. Nullable value types use int? or Nullable<int> syntax.
Database Initialization Strategies
public class StoreDbContext : DbContext
{
public StoreDbContext() : base("ConnectionStringName")
{
Database.SetInitializer(new DropCreateDatabaseIfModelChanges<StoreDbContext>());
}
}
Available initializers:
DropCreateDatabaseAlways<TContext>: Recreates database on every application startDropCreateDatabaseIfModelChanges<TContext>: Recreates only when model changesnull: Disables automatic initialization
Logging Generated SQL Commands
dbContext.Database.Log = sql => System.Diagnostics.Debug.WriteLine(sql);
This delegate captures all SQL statements executed by EF, including parameter values and execution time.
Eager Loading with Include
// Lambda expression syntax
var orders = dbContext.Orders.Include(o => o.Customer).ToList();
// String-based syntax
var products = dbContext.Products.Include("Category");
// Projection with related data
var summaries = dbContext.Orders
.Include(o => o.Customer)
.Select(o => new { o.OrderNumber, o.Customer.CompanyName, o.OrderDate });
Lazy Loading Configuration
Mark navigation properties as virtual to enable lazy loading:
public class Order
{
public int OrderId { get; set; }
public virtual ICollection<OrderItem> Items { get; set; }
}
Related entities load automatically when accessed for the first time.
Executing Raw SQL Commands
// Execute non-query commands
dbContext.Database.ExecuteSqlCommand(
"UPDATE Inventory SET StockCount = StockCount - {0} WHERE ProductId = {1}",
quantity, productId);
// Map query results to entities
var customerViews = dbContext.Database.SqlQuery<CustomerView>(
"SELECT CustomerId, CompanyName FROM Customers WHERE Country = @p0",
countryCode);
Antity Lifecycle States
Entities transition through these states:
- Detached: Not tracked by context
- Unchanged: Tracked, no modifications
- Added: New entity pending insertion
- Modified: Tracked entity with changes
- Deleted: Marked for deletion
State transitions:
Detached → Attached → Unchanged
Detached → Add → Added → SaveChanges → Unchanged
Unchanged → Modify → Modified → SaveChanges → Unchanged
Unchanged → Delete → Deleted → SaveChanges → Detached
Performance Optimization Techniques
Direct State Modification
// Bypass change detection
dbContext.Entry(existingProduct).State = EntityState.Deleted;
dbContext.SaveChanges();
Disable Change Tracking
var readOnlyData = dbContext.Products.AsNoTracking().Where(p => p.IsActive);
Suppress Validation
dbContext.Configuration.ValidateOnSaveEnabled = false;
Cascade Delete Control
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();
modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();
}
Generic DbSet Operations
public void BulkDelete<TEntity>(Expression<Func<TEntity, bool>> predicate)
where TEntity : class
{
var dbSet = dbContext.Set<TEntity>();
var entities = dbSet.Where(predicate);
dbSet.RemoveRange(entities);
}
The Set<T>() method provides type-safe access to entity sets when the type is determined at runtime.