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

Popular posts from this blog

Multiline to singleline IN C# - CODING

EF Core interview questions for beginners

EF Core interview questions for experienced