Teorie Avansată Senior Level - Entity Framework Core și .NET
Cuprins
- Entity Framework Core - Deep Dive
- .NET Runtime și Performance
- Advanced EF Core Patterns
- Optimizare și Profiling
- Advanced C# Concepts pentru Seniori
- Production Patterns și Best Practices
Entity Framework Core - Deep Dive
1. Change Tracking Mechanism
Cum Funcționează Change Tracking
Change tracking este procesul prin care EF Core monitorizează modificările pe entități.
public class ChangeTrackingDemo
{
private readonly AppDbContext _context;
// 1. SNAPSHOT TRACKING - default în EF Core
public void SnapshotTrackingExample()
{
var product = new Product { Id = 1, Name = "Laptop", Price = 1000 };
// EF creează o snapshot a valorilor originale
_context.Products.Add(product);
_context.SaveChanges(); // State = Unchanged
// Modificare
product.Price = 900;
// EF compară valoarea curentă cu snapshot-ul
var entry = _context.Entry(product);
Console.WriteLine(entry.State); // Modified
_context.SaveChanges();
}
// 2. NOTIFICATION TRACKING - cu INotifyPropertyChanged
public void NotificationTrackingExample()
{
// Entități care implementează INotifyPropertyChanged
// sunt notificate automat pe schimbări
}
// 3. AUTO TRACKING vs UNTRACKED QUERIES
public async Task TrackingVsUntracked()
{
// Cu tracking (default)
var product1 = await _context.Products
.FirstOrDefaultAsync(p => p.Id == 1);
// Se află în change tracker
// Fără tracking
var product2 = await _context.Products
.AsNoTracking()
.FirstOrDefaultAsync(p => p.Id == 1);
// Nu este tracked - mai rapid, RAM-ul mai mic
}
// 4. CHANGE TRACKER STATE MACHINE
public void StateTransitions()
{
var product = new Product { Name = "New" };
Console.WriteLine(_context.Entry(product).State); // Detached
_context.Products.Add(product);
Console.WriteLine(_context.Entry(product).State); // Added
_context.SaveChanges();
Console.WriteLine(_context.Entry(product).State); // Unchanged
product.Price = 100;
Console.WriteLine(_context.Entry(product).State); // Modified
_context.Products.Remove(product);
Console.WriteLine(_context.Entry(product).State); // Deleted
_context.SaveChanges();
Console.WriteLine(_context.Entry(product).State); // Detached
}
}
// States: Detached → Added → Unchanged → Modified → Deleted → Detached
Performance Implications
public class ChangeTrackerPerformance
{
private readonly AppDbContext _context;
// ❌ SLOW - Tracking 1 milion de entități
public async Task SlowBulkLoad()
{
var products = await _context.Products
.Where(p => p.Category == "Electronics")
.ToListAsync(); // Toate sunt tracked
// Memory spike - fiecare entitate are overhead
}
// ✅ FAST - Fără tracking
public async Task FastBulkLoad()
{
var products = await _context.Products
.AsNoTracking()
.Where(p => p.Category == "Electronics")
.Select(p => new ProductDTO { Id = p.Id, Name = p.Name })
.ToListAsync();
// Minimal memory usage
}
// ✅ OPTIMAL - Bulk operations
public async Task BulkUpdateEfficient()
{
// Utilizarea expresiilor pentru batch updates
await _context.Products
.Where(p => p.Price > 1000)
.ExecuteUpdateAsync(s => s.SetProperty(
p => p.Price,
p => p.Price * 0.9m // 10% discount
));
// Direct SQL, nu trec prin change tracker
}
}
2. Query Compilation și Execution
Expression Tree Compilation
public class QueryCompilation
{
private readonly AppDbContext _context;
// 1. COMPILED QUERIES - pentru queries repetate frecvent
public class CompiledQueries
{
private static readonly Func<AppDbContext, int, Task<Product>> GetProductById =
EF.CompileAsyncQuery((AppDbContext context, int id) =>
context.Products
.Include(p => p.Category)
.FirstOrDefault(p => p.Id == id));
public async Task<Product> GetProduct(int id)
{
// Compiled query - nu se recompile de fiecare dată
return await GetProductById(_context, id);
}
}
// 2. QUERY TAGS - pentru logging și debugging
public async Task<List<Product>> GetExpensiveProductsWithTag()
{
return await _context.Products
.TagWith("GetExpensiveProducts - from Admin Dashboard")
.Where(p => p.Price > 5000)
.ToListAsync();
// Tag-ul apare în query log
}
// 3. TRANSLATION TO SQL - ce se translate și ce nu
public void TranslationExamples()
{
// ✅ SE TRANSLATE - execută în DB
var query1 = _context.Products
.Where(p => p.Price > 100 && p.Category == "Electronics")
.Select(p => new { p.Id, p.Name });
// ⚠️ PARTIAL - filtrare în DB, apoi LINQ to Objects
var query2 = _context.Products
.Where(p => p.IsAvailable) // DB
.AsEnumerable()
.Where(p => SomeClientMethod(p.Name)) // Client-side
.ToList();
// ❌ NU SE TRANSLATE - LINQ to Objects pe toți produsele
var query3 = _context.Products
.AsEnumerable()
.Where(p => p.Price > 100)
.ToList();
}
private bool SomeClientMethod(string name) => !string.IsNullOrEmpty(name);
}
3. Relationships și Lazy Loading
Loaded vs Unloaded Navigation Properties
public class RelationshipManagement
{
private readonly AppDbContext _context;
// 1. LAZY LOADING - automat încarcă relații
public void LazyLoadingExample()
{
var product = _context.Products.Find(1);
// Accesul la Category declanșează o query separată
var categoryName = product.Category.Name; // 2 queries totale
}
// 2. EXPLICIT LOADING - manual control
public async Task ExplicitLoadingExample()
{
var product = _context.Products.Find(1);
// Numai dacă vrem
await _context.Entry(product)
.Reference(p => p.Category)
.LoadAsync();
var categoryName = product.Category.Name;
}
// 3. EAGER LOADING - Include
public async Task EagerLoadingExample()
{
var product = await _context.Products
.Include(p => p.Category)
.Include(p => p.Reviews)
.FirstOrDefaultAsync(p => p.Id == 1);
// Totul încărcat în 1 query cu JOIN-uri
}
// 4. FILTERED INCLUDES (EF Core 5.0+)
public async Task FilteredIncludesExample()
{
var product = await _context.Products
.Include(p => p.Reviews.Where(r => r.Rating >= 4))
.FirstOrDefaultAsync(p => p.Id == 1);
// Numai reviewuri cu 4+ stele
}
// 5. DEEP INCLUDES - relații înlănțuite
public async Task DeepIncludesExample()
{
var category = await _context.Categories
.Include(c => c.Products)
.ThenInclude(p => p.Reviews)
.ThenInclude(r => r.Author)
.FirstOrDefaultAsync(c => c.Id == 1);
// Category -> Products -> Reviews -> Author
}
// 6. COLLECTION VERSUS REFERENCE
public void CollectionVsReference()
{
var product = _context.Products.Find(1);
// Reference (many-to-one)
var category = product.Category; // Navigation property scalar
// Collection (one-to-many)
var reviews = product.Reviews; // Navigation property collection
}
}
4. Shadows Properties și Value Conversions
public class AdvancedConfiguration
{
// SHADOW PROPERTIES - proprietăți care nu sunt în clasă
public class ShadowPropertyExample
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.Property<DateTime>("CreatedAt")
.HasDefaultValueSql("GETUTCDATE()");
modelBuilder.Entity<Product>()
.Property<byte[]>("RowVersion")
.IsRowVersion();
}
// Accesare shadow property
public void AccessShadowProperty(Product product)
{
var entry = _context.Entry(product);
var createdAt = entry.Property("CreatedAt").CurrentValue;
var rowVersion = entry.Property("RowVersion").CurrentValue;
}
// Use cases:
// - Audit fields (CreatedAt, ModifiedAt, CreatedBy)
// - Concurrency tokens (RowVersion)
// - Technical fields neexpuse în API
}
// VALUE CONVERSIONS - transformare date în DB
public class ValueConversionExample
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Enum to String conversion
modelBuilder.Entity<Product>()
.Property(p => p.Status)
.HasConversion(
v => v.ToString(),
v => (ProductStatus)Enum.Parse(typeof(ProductStatus), v));
// Boolean to int conversion (pentru legacy DB)
modelBuilder.Entity<Product>()
.Property(p => p.IsActive)
.HasConversion(
v => v ? 1 : 0,
v => v == 1);
// Complex type to JSON
modelBuilder.Entity<Product>()
.Property(p => p.Metadata)
.HasConversion(
v => JsonConvert.SerializeObject(v),
v => JsonConvert.DeserializeObject<Dictionary<string, string>>(v));
// Encrypted values
modelBuilder.Entity<User>()
.Property(u => u.Email)
.HasConversion(
v => EncryptionService.Encrypt(v),
v => EncryptionService.Decrypt(v));
}
}
}
5. Query Filters Globali
public class GlobalQueryFiltersExample
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Soft delete - exclude deleted entities automat
modelBuilder.Entity<Product>()
.HasQueryFilter(p => !p.IsDeleted);
// Tenant-aware queries
modelBuilder.Entity<Order>()
.HasQueryFilter(o => o.TenantId == _currentTenantId);
// Published only
modelBuilder.Entity<BlogPost>()
.HasQueryFilter(p => p.IsPublished || p.AuthorId == _currentUserId);
}
// Disabling global filters când necesar
public async Task GetAllIncludingDeleted()
{
var all = await _context.Products
.IgnoreQueryFilters()
.ToListAsync();
}
}
6. Batch Operations și Performance
public class BatchOperationsPerformance
{
private readonly AppDbContext _context;
// BULK INSERT - cea mai rapidă metodă
public async Task BulkInsertOptimal()
{
var products = GenerateLargeList(100_000);
// Chunking - insert-uri în loturi
foreach (var chunk in products.Chunk(5000))
{
await _context.Products.AddRangeAsync(chunk);
await _context.SaveChangesAsync();
}
}
// BULK UPDATE
public async Task BulkUpdateOptimal()
{
await _context.Products
.Where(p => p.LastModified < DateTime.UtcNow.AddYears(-1))
.ExecuteUpdateAsync(s => s
.SetProperty(p => p.IsArchived, true)
.SetProperty(p => p.ArchivedDate, DateTime.UtcNow));
// Direct SQL update, bypasses change tracking
}
// BULK DELETE
public async Task BulkDeleteOptimal()
{
await _context.Products
.Where(p => p.IsArchived && p.ArchivedDate < DateTime.UtcNow.AddYears(-5))
.ExecuteDeleteAsync();
// Direct SQL delete
}
// COMPARISON - Memory usage
public class PerformanceComparison
{
// ❌ LENT - 100MB RAM, 50s pentru 100k items
public async Task SlowApproach()
{
var products = GenerateLargeList(100_000);
await _context.Products.AddRangeAsync(products);
await _context.SaveChangesAsync();
}
// ✅ RAPID - 5MB RAM, 2s pentru 100k items
public async Task FastApproach()
{
var products = GenerateLargeList(100_000);
foreach (var chunk in products.Chunk(1000))
{
await _context.Products.AddRangeAsync(chunk);
await _context.SaveChangesAsync();
_context.ChangeTracker.Clear();
}
}
// ✅✅ SUPER RAPID - 1MB RAM, 0.5s pentru 100k items
public async Task UltraFastApproach()
{
var bulk = new BulkInsertOperation<Product>(options =>
{
options.BatchSize = 1000;
options.EnableStreaming = true;
});
await bulk.InsertAsync(_context, GenerateLargeList(100_000));
}
}
}
.NET Runtime și Performance
1. Just-In-Time (JIT) Compilation
public class JITCompilationPatterns
{
// 1. TIERED JIT (C# 9+) - Two-level compilation
public class TieredJIT
{
// Tier 0: Rapid compilation, non-optimized code
// Tier 1: Slow compilation, optimized code
// Profiler-guided optimization (PGO) - .NET 6+
// Runtime colectează informații de profiling
// Recompilează cu optimizări specifice runtime-ului real
}
// 2. JITTED vs READY-TO-RUN (R2R)
public class ReadyToRunExample
{
// Ahead-of-Time (AOT) compilation
// .NET 8+ permite full AOT compilation
// Fără JIT - instant startup, predictable performance
/*
.csproj configuration:
<PublishAot>true</PublishAot>
Benefits:
- Instant startup (ms instead of s)
- No JIT pause times
- Smaller memory footprint
- Deterministic performance
Trade-offs:
- No reflection (except code-generated)
- No dynamic code generation
- Larger executable size
*/
}
// 3. HOT/COLD CODE SEPARATION
public class HotColdSeparation
{
public void FrequentCode()
{
// Optimized aggressively by JIT
// Este executat des
}
[System.Runtime.CompilerServices.MethodImpl(
System.Runtime.CompilerServices.MethodImplOptions.NoInlining)]
public void RareCode()
{
// Compilat la Tier 0, rar optimizat la Tier 1
// Este executat rar
}
}
}
2. Garbage Collection Tunning
public class GarbageCollectionTuning
{
// 1. GC MODES
public class GCModes
{
// Workstation GC (default)
// - Single concurrent GC thread
// - Low latency
// - Minimal resource usage
// Server GC
// - One GC thread per logical processor
// - Higher throughput
// - More predictable behavior
// Configuration in csproj:
/*
<PropertyGroup>
<ServerGarbageCollection>true</ServerGarbageCollection>
<RetainVMMemory>true</RetainVMMemory>
<GCRetainVMMemory>true</GCRetainVMMemory>
</PropertyGroup>
*/
}
// 2. GC CONFIGURATION
public class ConfigureGC
{
public void SetupGarbageCollection()
{
// Limit GC heap size
GCSettings.IsServerGC; // true/false
// Memory pressure notifications
var threshold = GCMemoryInfo.TotalMemory;
// Register for memory pressure notifications
GC.RegisterMemoryPressureCallback(MemoryPressureLevel.Medium, () =>
{
// Clear caches, cleanup resources
});
}
}
// 3. GC COLLECTIONS - Optimizare
public class OptimizeCollections
{
private List<byte[]> _cache = new();
// ❌ Cause excessive GC pressure
public void CreateGarbageInLoop()
{
for (int i = 0; i < 1_000_000; i++)
{
var data = new byte[1024]; // Allocated
_cache.Add(data); // Keeps reference
// Gen 0 collections spike
}
}
// ✅ Pre-allocate și reuse
public void OptimizedApproach()
{
var buffer = new byte[1024];
for (int i = 0; i < 1_000_000; i++)
{
Array.Clear(buffer);
ProcessData(buffer);
// No allocations in loop
}
}
// ✅ Use pooling
public void PoolingApproach()
{
using var pool = ArrayPool<byte>.Shared.Rent(1024);
for (int i = 0; i < 1_000_000; i++)
{
Array.Clear(pool);
ProcessData(pool);
}
ArrayPool<byte>.Shared.Return(pool);
}
}
}
3. Memory Optimization Strategies
public class AdvancedMemoryOptimization
{
// 1. STACKALLOC - allocare pe stack
public void StackAllocationExample()
{
// Stack allocation - fără GC
Span<int> numbers = stackalloc int[128];
for (int i = 0; i < numbers.Length; i++)
{
numbers[i] = i * i;
}
// Automat dealocated la iesire din scope
}
// 2. SPAN<T> și MEMORY<T>
public class SpanMemoryPattern
{
// ✅ No allocation - direct memory access
public void ProcessDataWithSpan(Span<byte> data)
{
// data poate fi:
// - Un array
// - Stack memory
// - Unmanaged memory
for (int i = 0; i < data.Length; i++)
{
data[i] = ProcessByte(data[i]);
}
}
// ✅ Owned memory - control lifecycle
public void ProcessWithOwnedMemory()
{
using (var memory = MemoryPool<byte>.Shared.Rent(1024))
{
var span = memory.Memory.Span;
ProcessDataWithSpan(span);
}
}
}
// 3. READONLY STRUCTS - Zero-copy parameters
public readonly struct DataPoint
{
public readonly int X;
public readonly int Y;
public readonly int Z;
// Passed by reference, no copying
}
// 4. STRUCT VS CLASS - Memory layout
public class MemoryLayout
{
// Class - Reference type (Heap)
// 24 bytes overhead + fields
// Garbage collected
// Struct - Value type (Stack or inline)
// Just the fields
// No GC overhead
// Rule: < 16 bytes = struct, > 16 bytes = class
}
}
4. Async Performance
public class AsyncPerformancePatterns
{
// 1. TASK POOL vs NEW TASKS
public async Task OptimalAsyncPattern()
{
// ✅ Reuse tasks from pool
var task = GetFromCacheAsync();
var result = await task;
// ❌ Allocate new Task
var task2 = new Task<int>(async () => await FetchAsync());
}
// 2. VALUETASK - Optimization pentru result synchronous
public class ValueTaskPattern
{
// ✅ ValueTask - avoid allocation if completed
public ValueTask<int> FastOperationAsync()
{
if (TryGetCached(out var result))
{
// No allocation - return directly
return new ValueTask<int>(result);
}
// Allocate Task only if async needed
return new ValueTask<int>(FetchFromDbAsync());
}
private bool TryGetCached(out int result)
{
result = 42;
return true;
}
private Task<int> FetchFromDbAsync()
{
return Task.FromResult(42);
}
}
// 3. CONFIGUREAWAIT IMPACT
public async Task ConfigureAwaitComparison()
{
// ❌ Capture context - potential overhead
var result = await FetchDataAsync();
// ✅ No context - skip synchronization context
var result2 = await FetchDataAsync().ConfigureAwait(false);
// In library code, always use ConfigureAwait(false)
}
// 4. CONCURRENT AWAIT vs SEQUENTIAL
public async Task OptimalConcurrency()
{
// ❌ Sequential - 3 seconds
var r1 = await Task1();
var r2 = await Task2();
var r3 = await Task3();
// ✅ Concurrent - 1 second
var tasks = new[] { Task1(), Task2(), Task3() };
var results = await Task.WhenAll(tasks);
// ✅ Alternative
var t1 = Task1();
var t2 = Task2();
var t3 = Task3();
var r1_alt = await t1;
var r2_alt = await t2;
var r3_alt = await t3;
}
}
Advanced EF Core Patterns
1. Multi-Tenancy
public class MultiTenancyPattern
{
// Tenant Context
public interface ITenantProvider
{
string GetTenantId();
}
// DbContext extension
public class MultiTenantDbContext : DbContext
{
private readonly ITenantProvider _tenantProvider;
public MultiTenantDbContext(DbContextOptions options, ITenantProvider tenantProvider)
: base(options)
{
_tenantProvider = tenantProvider;
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Automatic tenant filtering
var tenantId = _tenantProvider.GetTenantId();
modelBuilder.Entity<Order>()
.HasQueryFilter(o => o.TenantId == tenantId);
modelBuilder.Entity<Product>()
.HasQueryFilter(p => p.TenantId == tenantId);
}
}
// Usage
public class OrderService
{
private readonly MultiTenantDbContext _context;
private readonly ITenantProvider _tenantProvider;
public async Task<List<Order>> GetOrdersAsync()
{
// Automatically filtered by tenant
return await _context.Orders.ToListAsync();
}
}
}
2. Event Sourcing Integration
public class EventSourcingPattern
{
// Event store
public class EventStore
{
public int Id { get; set; }
public string AggregateId { get; set; }
public string EventType { get; set; }
public string EventData { get; set; }
public DateTime CreatedAt { get; set; }
public int Version { get; set; }
}
// Entity reconstruction from events
public class AggregateRoot
{
private List<Event> _uncommittedEvents = new();
protected void RaiseEvent(Event @event)
{
_uncommittedEvents.Add(@event);
Apply(@event);
}
protected virtual void Apply(Event @event)
{
// Override in subclasses
}
public IReadOnlyList<Event> GetUncommittedEvents() => _uncommittedEvents.AsReadOnly();
public void MarkEventsAsCommitted()
{
_uncommittedEvents.Clear();
}
}
// Persistence
public class EventSourcingRepository<T> where T : AggregateRoot, new()
{
private readonly AppDbContext _context;
public async Task SaveAsync(T aggregate)
{
var events = aggregate.GetUncommittedEvents();
foreach (var @event in events)
{
_context.EventStore.Add(new EventStore
{
AggregateId = aggregate.Id.ToString(),
EventType = @event.GetType().Name,
EventData = JsonConvert.SerializeObject(@event),
CreatedAt = DateTime.UtcNow
});
}
await _context.SaveChangesAsync();
aggregate.MarkEventsAsCommitted();
}
public async Task<T> GetAsync(string id)
{
var events = await _context.EventStore
.Where(e => e.AggregateId == id)
.OrderBy(e => e.Version)
.ToListAsync();
var aggregate = new T();
foreach (var storedEvent in events)
{
var @event = JsonConvert.DeserializeObject(
storedEvent.EventData,
Type.GetType(storedEvent.EventType));
// Reconstruct from events
}
return aggregate;
}
}
}
3. CQRS Pattern
public class CQRSPattern
{
// Command - mutație
public class CreateOrderCommand
{
public string CustomerId { get; set; }
public List<OrderLineDTO> Lines { get; set; }
}
// Command Handler
public class CreateOrderCommandHandler
{
private readonly AppDbContext _context;
private readonly IMediator _mediator;
public async Task HandleAsync(CreateOrderCommand command)
{
var order = new Order
{
CustomerId = command.CustomerId,
Lines = command.Lines.Select(l => new OrderLine
{
ProductId = l.ProductId,
Quantity = l.Quantity
}).ToList()
};
_context.Orders.Add(order);
await _context.SaveChangesAsync();
// Publish event
await _mediator.Publish(new OrderCreatedEvent { OrderId = order.Id });
}
}
// Query - citire, separate read model
public class GetOrdersQuery
{
public string CustomerId { get; set; }
}
// Query Handler - utiliza read model optimizat
public class GetOrdersQueryHandler
{
private readonly IQueryStore _queryStore;
public async Task<List<OrderDTO>> HandleAsync(GetOrdersQuery query)
{
// Citire din replicated, denormalized read model
return await _queryStore.Orders
.Where(o => o.CustomerId == query.CustomerId)
.ToListAsync();
}
}
}
4. Specification Pattern
public class SpecificationPattern
{
// Specification interface
public interface ISpecification<T>
{
Expression<Func<T, bool>> Criteria { get; }
List<Expression<Func<T, object>>> Includes { get; }
List<string> IncludeStrings { get; }
Expression<Func<T, object>> OrderBy { get; }
Expression<Func<T, object>> OrderByDescending { get; }
int? Take { get; }
int? Skip { get; }
bool IsPagingEnabled { get; }
}
// Base specification
public abstract class BaseSpecification<T> : ISpecification<T>
{
public Expression<Func<T, bool>> Criteria { get; protected set; }
public List<Expression<Func<T, object>>> Includes { get; } = new();
public List<string> IncludeStrings { get; } = new();
public Expression<Func<T, object>> OrderBy { get; protected set; }
public Expression<Func<T, object>> OrderByDescending { get; protected set; }
public int? Take { get; protected set; }
public int? Skip { get; protected set; }
public bool IsPagingEnabled { get; protected set; }
protected virtual void AddInclude(Expression<Func<T, object>> includeExpression)
{
Includes.Add(includeExpression);
}
protected virtual void AddInclude(string includeString)
{
IncludeStrings.Add(includeString);
}
protected virtual void ApplyPaging(int skip, int take)
{
Skip = skip;
Take = take;
IsPagingEnabled = true;
}
}
// Concrete specification
public class GetProductsWithCategorySpecification : BaseSpecification<Product>
{
public GetProductsWithCategorySpecification(string categoryName, int pageIndex, int pageSize)
{
Criteria = p => p.Category.Name == categoryName;
AddInclude(p => p.Category);
AddInclude(p => p.Reviews);
OrderBy = p => p.Name;
ApplyPaging(pageIndex * pageSize, pageSize);
}
}
// Repository implementation
public class EFRepository<T> where T : class
{
private readonly AppDbContext _context;
public async Task<List<T>> ListAsync(ISpecification<T> specification)
{
return await ApplySpecification(specification).ToListAsync();
}
public async Task<int> CountAsync(ISpecification<T> specification)
{
return await ApplySpecification(specification).CountAsync();
}
private IQueryable<T> ApplySpecification(ISpecification<T> spec)
{
var query = _context.Set<T>().AsQueryable();
if (spec.Criteria != null)
query = query.Where(spec.Criteria);
query = spec.Includes.Aggregate(query, (current, include) =>
current.Include(include));
query = spec.IncludeStrings.Aggregate(query, (current, include) =>
current.Include(include));
if (spec.OrderBy != null)
query = query.OrderBy(spec.OrderBy);
if (spec.OrderByDescending != null)
query = query.OrderByDescending(spec.OrderByDescending);
if (spec.IsPagingEnabled)
{
if (spec.Skip.HasValue)
query = query.Skip(spec.Skip.Value);
if (spec.Take.HasValue)
query = query.Take(spec.Take.Value);
}
return query;
}
}
}
5. Interceptors
public class InterceptorPatterns
{
// Audit interceptor
public class AuditInterceptor : SaveChangesInterceptor
{
private readonly ICurrentUserService _currentUserService;
public override ValueTask<InterceptionResult<int>> SavingChangesAsync(
DbContextEventData eventData,
InterceptionResult<int> result,
CancellationToken cancellationToken = default)
{
var context = eventData.Context;
foreach (var entry in context.ChangeTracker.Entries())
{
if (entry.Entity is IAuditable auditable)
{
if (entry.State == EntityState.Added)
{
auditable.CreatedAt = DateTime.UtcNow;
auditable.CreatedBy = _currentUserService.GetUserId();
}
if (entry.State == EntityState.Modified)
{
auditable.ModifiedAt = DateTime.UtcNow;
auditable.ModifiedBy = _currentUserService.GetUserId();
}
}
}
return base.SavingChangesAsync(eventData, result, cancellationToken);
}
}
// Query logging interceptor
public class QueryLoggingInterceptor : DbCommandInterceptor
{
private readonly ILogger<QueryLoggingInterceptor> _logger;
public override ValueTask<InterceptionResult<DbDataReader>> ReaderExecutedAsync(
DbCommand command,
CommandExecutedEventData eventData,
InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = default)
{
_logger.LogInformation($"Query executed: {command.CommandText}");
_logger.LogInformation($"Duration: {eventData.Duration.TotalMilliseconds}ms");
return base.ReaderExecutedAsync(command, eventData, result, cancellationToken);
}
}
// Retry logic interceptor
public class RetryInterceptor : DbCommandInterceptor
{
public override async ValueTask<InterceptionResult<DbDataReader>> ReaderExecutingAsync(
DbCommand command,
CommandEventData eventData,
InterceptionResult<DbDataReader> result,
CancellationToken cancellationToken = default)
{
int retryCount = 0;
const int maxRetries = 3;
while (retryCount < maxRetries)
{
try
{
return await base.ReaderExecutingAsync(command, eventData, result, cancellationToken);
}
catch (InvalidOperationException) when (retryCount < maxRetries - 1)
{
retryCount++;
await Task.Delay(100 * retryCount);
}
}
return result;
}
}
}
Optimizare și Profiling
1. Database Query Optimization
public class DatabaseOptimization
{
// Query profiling
public class QueryProfiler
{
private readonly AppDbContext _context;
public async Task<QueryProfile> ProfileQueryAsync<T>(
IQueryable<T> query) where T : class
{
var sw = Stopwatch.StartNew();
var sql = query.ToQueryString();
Console.WriteLine($"Generated SQL:\n{sql}");
var result = await query.ToListAsync();
sw.Stop();
return new QueryProfile
{
GeneratedSql = sql,
ExecutionTime = sw.ElapsedMilliseconds,
RowCount = result.Count
};
}
}
// Index recommendations
public class IndexOptimization
{
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Frequent filters
modelBuilder.Entity<Product>()
.HasIndex(p => p.CategoryId)
.HasName("IX_Product_CategoryId");
// Composite indexes
modelBuilder.Entity<OrderLine>()
.HasIndex(ol => new { ol.OrderId, ol.ProductId })
.HasName("IX_OrderLine_OrderId_ProductId");
// Unique indexes
modelBuilder.Entity<User>()
.HasIndex(u => u.Email)
.IsUnique()
.HasName("IX_User_Email_Unique");
// Filtered indexes
modelBuilder.Entity<Order>()
.HasIndex(o => o.CustomerId)
.HasFilter("[IsArchived] = 0")
.HasName("IX_Order_CustomerId_Active");
}
}
}
2. Benchmarking
[MemoryDiagnoser]
public class PerformanceBenchmarks
{
private AppDbContext _context;
private List<Product> _products;
[GlobalSetup]
public void Setup()
{
_context = new AppDbContext();
_products = GenerateTestData(1000);
}
[Benchmark]
public async Task QueryWithTracking()
{
var result = await _context.Products
.Where(p => p.Price > 100)
.ToListAsync();
}
[Benchmark]
public async Task QueryWithoutTracking()
{
var result = await _context.Products
.AsNoTracking()
.Where(p => p.Price > 100)
.ToListAsync();
}
[Benchmark]
public async Task QueryWithProjection()
{
var result = await _context.Products
.Where(p => p.Price > 100)
.Select(p => new { p.Id, p.Name })
.ToListAsync();
}
// Usage:
// var summary = BenchmarkRunner.Run<PerformanceBenchmarks>();
}
Advanced C# Concepts pentru Seniori
1. Records - Immutable Reference Types
// Positional record - perfect pentru DTO-uri
public record OrderDto(int Id, string CustomerName, decimal Total);
// Init-only properties
public record ProductRecord
{
public int Id { get; init; }
public string Name { get; init; }
public decimal Price { get; init; }
}
// Record inheritance
public record PremiumOrderDto(int Id, string CustomerName, decimal Total, decimal Discount)
: OrderDto(Id, CustomerName, Total);
// Value equality - FREE from record syntax
var order1 = new OrderDto(1, "John", 100);
var order2 = new OrderDto(1, "John", 100);
Console.WriteLine(order1 == order2); // true - value equality
// With-expressions - immutable updates
var updatedOrder = order1 with { Total = 150 };
2. Pattern Matching Advanced
public class PatternMatchingAdvanced
{
// Property patterns
public string AnalyzeProduct(Product product) =>
product switch
{
{ Price: > 1000, Category: "Electronics" } => "Premium Electronics",
{ Price: > 100, IsAvailable: true } => "Available Premium",
{ IsArchived: true } => "Archived",
_ => "Standard"
};
// List patterns (C# 9+)
public bool CheckPrices(int[] prices) =>
prices switch
{
[100, 200, 300] => true,
[var first, .. var rest] when rest.All(x => x > first) => true,
[.., var last] when last > 1000 => true,
_ => false
};
// Relational patterns
public string CategorizeByPrice(Product product) =>
product.Price switch
{
< 50 => "Cheap",
>= 50 and < 200 => "Mid-range",
>= 200 and < 1000 => "Expensive",
>= 1000 => "Premium",
};
// Type patterns with guards
public void ProcessEntity(object entity) =>
_ = entity switch
{
Customer { Age: < 18 } cust => HandleMinor(cust),
Customer { Age: >= 65 } cust => HandleSenior(cust),
Product { Price: > 1000 } prod => HandlePremium(prod),
Product => HandleRegularProduct(),
_ => HandleUnknown()
};
}
3. Source Generators
// Custom attribute for generation
[AttributeUsage(AttributeTargets.Class)]
public class GenerateToStringAttribute : Attribute { }
// Usage
[GenerateToString]
public partial class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
// Source generator generates at compile-time:
// public partial class User
// {
// public override string ToString()
// => $"User {{ Id = {Id}, Name = {Name}, Email = {Email} }}";
// }
4. Nullable Reference Types (Advanced)
#nullable enable
public class NullableAdvanced
{
// Non-null by default
public string Name { get; set; } // Cannot be null
public string? Email { get; set; } // Can be null
// Null-forgiving operator
public void Process(string? input)
{
var length = input!.Length; // Compiler warning suppressed
}
// Null coalescing assignment
public void EnsureValue()
{
Name ??= "Default";
}
// Pattern matching null checks
public bool IsValid(User? user) =>
user is not null && !string.IsNullOrEmpty(user.Email);
}
#nullable disable
5. Structural Types & Performance
// Struct for small data aggregates
public readonly struct CoordinateStruct
{
public int X { get; }
public int Y { get; }
public CoordinateStruct(int x, int y)
{
X = x;
Y = y;
}
}
// Reference type for larger objects
public class CoordinateClass
{
public int X { get; set; }
public int Y { get; set; }
}
public class StructVsClassComparison
{
public void PerformanceDemo()
{
// Struct - value type (usually stack)
var structs = new CoordinateStruct[1000000];
// ~8MB (2 ints * 1M)
// Class - reference type (heap)
var classes = new CoordinateClass[1000000];
// ~8MB references + heap allocations
// = Many MB more due to object overhead
}
}
Production Patterns și Best Practices
1. Health Checks
public class HealthCheckPatterns
{
// Register in Startup
public void ConfigureServices(IServiceCollection services)
{
services
.AddHealthChecks()
.AddDbContextCheck<AppDbContext>()
.AddCheck<CustomHealthCheck>("custom");
}
// Custom health check
public class DatabaseHealthCheck : IHealthCheck
{
private readonly AppDbContext _context;
public async Task<HealthCheckResult> CheckHealthAsync(
HealthCheckContext context,
CancellationToken cancellationToken = default)
{
try
{
var canConnect = await _context.Database.CanConnectAsync(cancellationToken);
if (!canConnect)
return HealthCheckResult.Unhealthy("Cannot connect to database");
var query = await _context.Database
.ExecuteQueryRawAsync("SELECT 1", cancellationToken);
return HealthCheckResult.Healthy("Database is healthy");
}
catch (Exception ex)
{
return HealthCheckResult.Unhealthy("Database error", ex);
}
}
}
}
2. Circuit Breaker Pattern
public class CircuitBreakerPattern
{
// Using Polly
public class ResilientHttpClient
{
private readonly IAsyncPolicy<HttpResponseMessage> _policy;
public ResilientHttpClient()
{
var retryPolicy = Policy
.Handle<HttpRequestException>()
.Or<TimeoutRejectedException>()
.OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: attempt =>
TimeSpan.FromSeconds(Math.Pow(2, attempt)));
var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.OrResult<HttpResponseMessage>(r => !r.IsSuccessStatusCode)
.CircuitBreakerAsync<HttpResponseMessage>(
handledEventsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromSeconds(30));
_policy = Policy.WrapAsync(retryPolicy, circuitBreakerPolicy);
}
public async Task<T> GetAsync<T>(string url)
{
var response = await _policy.ExecuteAsync(() =>
_client.GetAsync(url));
var content = await response.Content.ReadAsStringAsync();
return JsonConvert.DeserializeObject<T>(content);
}
}
}
3. Distributed Caching Strategy
public class DistributedCachingStrategy
{
private readonly IDistributedCache _cache;
// Cache-aside pattern
public async Task<Product> GetProductAsync(int id)
{
var cacheKey = $"product_{id}";
// Try get from cache
var cached = await _cache.GetStringAsync(cacheKey);
if (!string.IsNullOrEmpty(cached))
{
return JsonConvert.DeserializeObject<Product>(cached);
}
// Get from database
var product = await _context.Products.FindAsync(id);
if (product != null)
{
// Store in cache
await _cache.SetStringAsync(
cacheKey,
JsonConvert.SerializeObject(product),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(1)
});
}
return product;
}
// Cache invalidation
public async Task InvalidateProductAsync(int id)
{
var cacheKey = $"product_{id}";
await _cache.RemoveAsync(cacheKey);
}
}
4. Structured Logging
public class StructuredLoggingBestPractices
{
private readonly ILogger<OrderService> _logger;
public async Task ProcessOrderAsync(Order order)
{
using (_logger.BeginScope(
new Dictionary<string, object>
{
{ "OrderId", order.Id },
{ "CustomerId", order.CustomerId },
{ "CorrelationId", HttpContext.TraceIdentifier }
}))
{
try
{
_logger.LogInformation(
"Starting order processing. Amount: {Amount}, Items: {ItemCount}",
order.Total,
order.Lines.Count);
await ValidateOrderAsync(order);
_logger.LogDebug("Order validation passed");
await ProcessPaymentAsync(order);
_logger.LogInformation("Order processed successfully");
}
catch (InvalidOperationException ex)
{
_logger.LogError(
ex,
"Order processing failed. Reason: {Reason}",
ex.Message);
throw;
}
catch (Exception ex)
{
_logger.LogCritical(
ex,
"Unexpected error during order processing");
throw;
}
}
}
}
5. Request/Response Middleware
public class RequestResponseLoggingMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<RequestResponseLoggingMiddleware> _logger;
public RequestResponseLoggingMiddleware(RequestDelegate next,
ILogger<RequestResponseLoggingMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context)
{
// Capture request
context.Request.EnableBuffering();
var requestBody = await new StreamReader(context.Request.Body).ReadToEndAsync();
context.Request.Body.Position = 0;
_logger.LogInformation(
"HTTP {Method} {Path} - Body: {Body}",
context.Request.Method,
context.Request.Path,
requestBody);
// Capture response
var originalBodyStream = context.Response.Body;
using (var responseBody = new MemoryStream())
{
context.Response.Body = responseBody;
await _next(context);
var response = await new StreamReader(context.Response.Body).ReadToEndAsync();
context.Response.Body.Position = 0;
_logger.LogInformation(
"HTTP Response {StatusCode} - Body: {Body}",
context.Response.StatusCode,
response);
await responseBody.CopyToAsync(originalBodyStream);
}
}
}
Concluzie - Diferențe Senior Developer
Un senior developer cu C# și .NET trebuie să:
-
Performance First Mindset
- Cunoaște GC behavior
- Evită allocations inutile
- Profile și optimize
-
Architecture Thinking
- CQRS, Event Sourcing
- Microservices patterns
- Distributed systems
-
Production Ready Code
- Error handling robust
- Logging structured
- Health checks și monitoring
-
Database Mastery
- Query optimization
- Indexing strategies
- Transaction handling
-
Advanced Patterns
- Multi-tenancy
- Circuit breakers
- Resilience patterns
-
Mentoring Capability
- Explică complex topics
- Code review pe inalt nivel
- Architecture decisions
Data: 2025 C# Version: 12 .NET Version: 8+