EF Core Coding Example
EF Core Coding Example
Shadow properties in EF Core:
Define shadow properties for tracking audit information and Automatically set the current date when created
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Product>()
.Property<DateTime>("CreatedDate")
.HasDefaultValueSql("GETDATE()");
}
AsNoTracking method in EF Core:
Method to get data without tracking changes and Using AsNoTracking to improve performance and prevent change tracking for a read-only operation.
public List<Product> GetAllProducts()
{
return _context.Products.AsNoTracking().ToList();
}
public Product GetProductById(int id)
{
return _context.Products.AsNoTracking().FirstOrDefault(p => p.Id == id);
}
What is the importance of DbContext disposal, and how do you manage it effectively?
// Register DbContext with scoped lifetime
builder.Services.AddDbContext<StoreDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))
);
// Register other services
builder.Services.AddScoped<ProductService>();
How can you implement versioning in EF Core to handle data changes across multiple users?
// Entity with RowVersion for optimistic concurrency control
public class Product
{
public int Id {
get; set; }
public string Name
{ get; set; }
public decimal
Price { get; set; }
// RowVersion
property for concurrency control
[Timestamp]
public byte[]
RowVersion { get; set; }
}
// DbContext configuration for RowVersion
public class StoreDbContext : DbContext
{
public
DbSet<Product> Products { get; set; }
public
StoreDbContext(DbContextOptions<StoreDbContext> options)
:
base(options)
{ }
protected override
void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<Product>()
.Property(p => p.RowVersion)
.IsRowVersion(); // Configure RowVersion property as concurrency token
}
}
// Service method to update product with concurrency
handling
public class ProductService
{
private readonly
StoreDbContext _context;
public
ProductService(StoreDbContext context)
{
_context =
context;
}
public async
Task<Product> UpdateProductAsync(int id, Product updatedProduct)
{
var product =
await _context.Products.FindAsync(id);
if (product ==
null)
{
throw new
Exception("Product not found.");
}
// Update
product properties
product.Name =
updatedProduct.Name;
product.Price
= updatedProduct.Price;
try
{
await
_context.SaveChangesAsync(); // Save changes and check for concurrency
conflicts
}
catch
(DbUpdateConcurrencyException)
{
throw new
Exception("The product was modified by another user.");
}
return
product;
}
}
// Controller for handling product updates
[ApiController]
[Route("api/[controller]")]
public class ProductsController : ControllerBase
{
private readonly
ProductService _productService;
public
ProductsController(ProductService productService)
{
_productService = productService;
}
[HttpPut("{id}")]
public async
Task<IActionResult> UpdateProduct(int id, Product updatedProduct)
{
try
{
var
product = await _productService.UpdateProductAsync(id, updatedProduct);
return
Ok(product);
}
catch
(Exception ex)
{
return
Conflict(new { message = ex.Message });
}
}
}
1. Product
Entity:
o The
Product class has a RowVersion property which is marked with the [Timestamp] attribute.
This is used to handle concurrency, ensuring that the data isn't overwritten if
it has been modified by another user.
2. DbContext:
o In
StoreDbContext, the RowVersion property is configured using IsRowVersion() to
tell EF Core that this property will be used for concurrency control.
3. ProductService:
o The
UpdateProductAsync method retrieves a Product by Id. When updating, it checks
for any concurrency conflicts using SaveChangesAsync(). If the RowVersion has
changed since it was loaded, it will throw a DbUpdateConcurrencyException.
4. ProductsController:
o The
ProductsController exposes an API endpoint (PUT /api/products/{id}) to update a
product. It handles any concurrency conflicts and returns an appropriate
message if the update fails due to a conflict.
This setup ensures that EF Core automatically checks for and
handles conflicts when two users attempt to modify the same data
simultaneously.
What is the importance of DbContext disposal, and how do you manage it effectively?
// Importance of DbContext Disposal:
// DbContext should be disposed of properly to release unmanaged resources like database connections.
// It is essential to manage DbContext lifecycle to avoid memory leaks and inefficient queries.
public class ProductService
{
private readonly StoreDbContext _context;
// DbContext is injected via dependency injection
public ProductService(StoreDbContext context)
{
_context = context;
}
// The DbContext will be automatically disposed of by the DI container when the scope ends (e.g., end of HTTP request)
public Product GetProduct(int id)
{
return _context.Products.Find(id);
}
}
// In ASP.NET Core, DbContext is managed via Dependency Injection (DI) with Scoped lifetime.
// DbContext will be disposed automatically after the request is handled, preventing memory leaks.
public class Startup
{
public void ConfigureServices(IServiceCollection services)
{
// Register DbContext with Scoped lifetime
services.AddDbContext<StoreDbContext>(options =>
options.UseSqlServer("YourConnectionString"));
// Register other services
services.AddScoped<ProductService>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
// Configure middleware (automatic disposal at the end of the request)
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
// Scoped lifetime ensures DbContext is disposed properly at the end of each request.
// It prevents issues such as memory leaks and database connection exhaustion.
public class ProductsController : ControllerBase
{
private readonly ProductService _productService;
// ProductService injected, which contains DbContext dependency
public ProductsController(ProductService productService)
{
_productService = productService;
}
[HttpGet("{id}")]
public IActionResult GetProduct(int id)
{
var product = _productService.GetProduct(id);
if (product == null)
{
return NotFound();
}
return Ok(product);
}
}
Key Points:
• DbContext Disposal: Automatically handled by the DI container when registered as Scoped.
• Scoped Lifetime: Ensures DbContext is created per request and disposed of automatically, avoiding memory leaks.
Explain the concept of Change Tracking in EF Core and how you can optimize it.
// Example of Change Tracking in EF Core and optimization techniques
// Entity class
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
// Optimizing Change Tracking with AsNoTracking for read-only queries
public class ProductService
{
private readonly StoreDbContext _context;
public ProductService(StoreDbContext context)
{
_context = context;
}
// AsNoTracking is used for read-only queries to improve performance by disabling change tracking
public IEnumerable<Product> GetProducts()
{
return _context.Products.AsNoTracking().ToList();
}
}
// Disabling change tracking globally for specific scenarios (e.g., bulk operations)
public class StoreDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public StoreDbContext(DbContextOptions<StoreDbContext> options)
: base(options)
{ }
// Disable change tracking for bulk insert/update/delete operations
public void DisableChangeTracking()
{
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
}
// Reset change tracking behavior back to default
public void EnableChangeTracking()
{
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.TrackAll;
}
}
// Example of using DisableChangeTracking for bulk operations
public class ProductService
{
private readonly StoreDbContext _context;
public ProductService(StoreDbContext context)
{
_context = context;
}
public void BulkUpdate(IEnumerable<Product> products)
{
_context.DisableChangeTracking();
foreach (var product in products)
{
var existingProduct = _context.Products.Find(product.Id);
if (existingProduct != null)
{
existingProduct.Name = product.Name;
existingProduct.Price = product.Price;
}
}
_context.SaveChanges();
_context.EnableChangeTracking(); // Re-enable change tracking after bulk operation
}
}
Key Points:
• Change Tracking: EF Core tracks entity changes (added, modified, deleted) by default, which impacts performance, especially with large datasets.
• Optimization:
o AsNoTracking: Used for read-only queries to avoid tracking, improving performance.
o Disable Change Tracking: For bulk operations, disabling change tracking can reduce overhead.
o Global Change Tracking Settings: Change tracking can be disabled globally for specific scenarios like bulk updates.
// Entity class with primary key
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
// DbContext configuration
public class StoreDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public StoreDbContext(DbContextOptions<StoreDbContext> options)
: base(options)
{ }
}
// Identity resolution example: EF Core ensures no duplicate entities with the same primary key
public class ProductService
{
private readonly StoreDbContext _context;
public ProductService(StoreDbContext context)
{
_context = context;
}
public void LoadProducts()
{
// EF Core will ensure identity resolution by tracking a single instance of the entity with the same primary key
var product1 = _context.Products.FirstOrDefault(p => p.Id == 1);
var product2 = _context.Products.FirstOrDefault(p => p.Id == 1);
// product1 and product2 will be the same instance in memory
if (ReferenceEquals(product1, product2))
{
Console.WriteLine("Both references point to the same object.");
}
}
}
// Prevent duplicate objects by using the same DbContext instance
public class ProductService
{
private readonly StoreDbContext _context;
public ProductService(StoreDbContext context)
{
_context = context;
}
public void AvoidDuplicates()
{
// Using the same DbContext ensures no duplicate entities with the same primary key
var product1 = _context.Products.Find(1); // Querying once
var product2 = _context.Products.Find(1); // Querying the same entity within the same DbContext
// Both product1 and product2 will refer to the same instance
if (ReferenceEquals(product1, product2))
{
Console.WriteLine("No duplicate objects - both refer to the same instance.");
}
}
}
Key Points:
• Identity Resolution: EF Core ensures that each entity is tracked only once by its primary key.
• Prevent Duplicates: Use the same DbContext instance within a unit of work to avoid querying the same entity multiple times, preventing duplicate objects.
How does EF Core handle identity resolution, and how can you prevent duplicate objects?
// Entity class with primary key
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
// DbContext configuration
public class StoreDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public StoreDbContext(DbContextOptions<StoreDbContext> options)
: base(options)
{ }
}
// Identity resolution example: EF Core ensures no duplicate entities with the same primary key
public class ProductService
{
private readonly StoreDbContext _context;
public ProductService(StoreDbContext context)
{
_context = context;
}
public void LoadProducts()
{
// EF Core will ensure identity resolution by tracking a single instance of the entity with the same primary key
var product1 = _context.Products.FirstOrDefault(p => p.Id == 1);
var product2 = _context.Products.FirstOrDefault(p => p.Id == 1);
// product1 and product2 will be the same instance in memory
if (ReferenceEquals(product1, product2))
{
Console.WriteLine("Both references point to the same object.");
}
}
}
// Prevent duplicate objects by using the same DbContext instance
public class ProductService
{
private readonly StoreDbContext _context;
public ProductService(StoreDbContext context)
{
_context = context;
}
public void AvoidDuplicates()
{
// Using the same DbContext ensures no duplicate entities with the same primary key
var product1 = _context.Products.Find(1); // Querying once
var product2 = _context.Products.Find(1); // Querying the same entity within the same DbContext
// Both product1 and product2 will refer to the same instance
if (ReferenceEquals(product1, product2))
{
Console.WriteLine("No duplicate objects - both refer to the same instance.");
}
}
}
Key Points:
• Identity Resolution: EF Core ensures that each entity is tracked only once by its primary key.
• Prevent Duplicates: Use the same DbContext instance within a unit of work to avoid querying the same entity multiple times, preventing duplicate objects.
What is TrackingBehavior in EF Core, and how do you use it?
// Entity class
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
}
// DbContext configuration with TrackingBehavior control
public class StoreDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public StoreDbContext(DbContextOptions<StoreDbContext> options)
: base(options)
{ }
// Example of setting the global tracking behavior
public void SetTrackingBehavior()
{
// Set global tracking behavior to NoTracking (no change tracking)
ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
}
}
// Using AsTracking and AsNoTracking for individual queries
public class ProductService
{
private readonly StoreDbContext _context;
public ProductService(StoreDbContext context)
{
_context = context;
}
// AsTracking ensures change tracking is enabled for the query
public Product GetProductWithTracking(int id)
{
return _context.Products.AsTracking().FirstOrDefault(p => p.Id == id);
}
// AsNoTracking ensures no change tracking, improving performance for read-only queries
public Product GetProductWithoutTracking(int id)
{
return _context.Products.AsNoTracking().FirstOrDefault(p => p.Id == id);
}
}
// Example of using NoTracking for performance optimization
public class ProductService
{
private readonly StoreDbContext _context;
public ProductService(StoreDbContext context)
{
_context = context;
}
// Using NoTracking for read-only queries to improve performance
public IEnumerable<Product> GetAllProducts()
{
return _context.Products.AsNoTracking().ToList();
}
}
Key Points:
• TrackingBehavior controls how EF Core tracks entity changes.
o TrackAll (default): EF Core tracks all entities.
o NoTracking: No change tracking, improves performance for read-only queries.
• You can set QueryTrackingBehavior globally or for individual queries using AsTracking() or AsNoTracking().
What is the purpose of the HasQueryFilter method in EF Core, and when would you use it?
// Entity class with soft delete feature
public class Product
{
public int Id { get; set; }
public string Name { get; set; }
public decimal Price { get; set; }
// Soft delete flag
public bool IsDeleted { get; set; }
}
// DbContext configuration with HasQueryFilter for soft deletes
public class StoreDbContext : DbContext
{
public DbSet<Product> Products { get; set; }
public StoreDbContext(DbContextOptions<StoreDbContext> options)
: base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Apply global query filter for soft deletes
modelBuilder.Entity<Product>()
.HasQueryFilter(p => !p.IsDeleted);
}
}
// Service layer that uses the global query filter
public class ProductService
{
private readonly StoreDbContext _context;
public ProductService(StoreDbContext context)
{
_context = context;
}
// Get products excluding the soft-deleted ones (filter is applied automatically)
public IEnumerable<Product> GetAllProducts()
{
return _context.Products.ToList();
}
// Get a single product by ID, soft deleted items are excluded
public Product GetProductById(int id)
{
return _context.Products.FirstOrDefault(p => p.Id == id);
}
}
// Example of disabling query filters for specific queries (e.g., for restoring soft deleted entities)
public class ProductService
{
private readonly StoreDbContext _context;
public ProductService(StoreDbContext context)
{
_context = context;
}
// Get all products, including soft-deleted ones
public IEnumerable<Product> GetAllProductsIncludingDeleted()
{
return _context.Products.IgnoreQueryFilters().ToList(); // Disables query filter
}
}
Key Points:
• HasQueryFilter: Defines a global filter that applies automatically to all queries for an entity.
• Use Cases:
o Soft deletes: Automatically excludes deleted entities from results.
o Multi-tenant applications: Filters data based on the tenant.
o Filtering sensitive data: Ensures consistent application of privacy rules across queries.
How do you implement and configure composite keys in EF Core?
// Entity class with composite key
public class OrderItem
{
public int OrderId { get; set; }
public int ProductId { get; set; }
public int Quantity { get; set; }
// Other properties related to the order item
}
// DbContext configuration with composite key
public class StoreDbContext : DbContext
{
public DbSet<OrderItem> OrderItems { get; set; }
public StoreDbContext(DbContextOptions<StoreDbContext> options)
: base(options)
{ }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// Configure composite primary key using Fluent API
modelBuilder.Entity<OrderItem>()
.HasKey(oi => new { oi.OrderId, oi.ProductId }); // Composite key
}
}
// Service example to use OrderItem entity with composite key
public class OrderItemService
{
private readonly StoreDbContext _context;
public OrderItemService(StoreDbContext context)
{
_context = context;
}
// Add a new order item with a composite key
public void AddOrderItem(int orderId, int productId, int quantity)
{
var orderItem = new OrderItem
{
OrderId = orderId,
ProductId = productId,
Quantity = quantity
};
_context.OrderItems.Add(orderItem);
_context.SaveChanges();
}
// Get an order item by composite key
public OrderItem GetOrderItem(int orderId, int productId)
{
return _context.OrderItems
.FirstOrDefault(oi => oi.OrderId == orderId && oi.ProductId == productId);
}
}
Key Points:
• Composite Key: Implemented using the HasKey method in the Fluent API with multiple properties.
• Example: modelBuilder.Entity<OrderItem>().HasKey(oi => new { oi.OrderId, oi.ProductId }); defines a composite key using OrderId and ProductId.
How does EF Core handle migrations when working in a team environment?
Common practices include creating separate branches for different features, generating and applying migrations individually, and regularly checking for conflicts.
# Step 1: Create a new branch for a feature
git checkout -b feature/new-feature
# Step 2: Make changes to your models and add a migration
dotnet ef migrations add AddNewFeature --context YourDbContext
# Step 3: Apply the migration to the database
dotnet ef database update --context YourDbContext
# Step 4: Commit the changes to the feature branch
git add .
git commit -m "Added new feature with migration"
# Step 5: Push the branch to the remote repository
git push origin feature/new-feature
# Step 6: Regularly check for conflicts and merge updates from the main branch
git checkout main
git pull origin main
# Step 7: Merge changes from main to your feature branch to avoid conflicts
git checkout feature/new-feature
git merge main
# Step 8: Resolve any merge conflicts if necessary, then push the updates
git push origin feature/new-feature
# Step 9: Once the feature is complete, create a pull request to merge it into main
Key Practices:
• Separate branches for features: Helps isolate changes and maintain a clean project history.
• Generate and apply migrations individually: Ensures migrations are versioned and applied in an isolated, controlled manner.
• Check for conflicts regularly: Prevents issues when integrating multiple changes from different developers.
Comments
Post a Comment