Entity FRAMEWORK CORE

Entity Framework Core (EF Core) is an open-source, lightweight, and object-relational mapping (ORM) framework for .NET. It eliminates the need for most of the data-access code. EF Core simplifies database interaction by allowing developers to work with data as objects instead of writing raw SQL queries. EF Core is cross-platform and supports different database providers, such as SQL Server, SQLite, MySQL, PostgreSQL, and others.

 

EF Core features:

  • Code-First Development: Allows defining entities and relationships using plain C# or VB.NET classes, and generating the database schema from these classes.
  • Database-First Development: Enables generating entity classes and DbContext from an existing database schema.
  • LINQ Support: Provides support for LINQ (Language Integrated Query) queries, allowing developers to query databases using C# or VB.NET syntax.
  • Change Tracking: Automatically tracks changes made to entities, and facilitates persisting these changes to the database.
  • Concurrency Control: Supports optimistic concurrency control, allowing multiple users to work with the same data concurrently.
  • Transactions: Supports transactions for ensuring data consistency and atomicity.
  • Inheritance Mapping: Supports various strategies for mapping class hierarchies to database tables.

 

 

Entities:

An entity in EF Core represents a data model or entity classes. Each entity class represents a database table, and its properties represent columns in that table. These are C# classes with properties that map to the table's columns.

public class Product

   {

       public int Id { get; set; }

       public string Name { get; set; }

       public decimal Price { get; set; }

   }

 

 

Relationships between entities can be configured using navigation properties and either Fluent API or Data Annotations. Fluent API provides a more flexible and powerful way to define relationships compared to Data Annotations. For example, to configure a one-to-many relationship between Order and Product entities:

Example:

   modelBuilder.Entity<Order>()
       .HasMany(o => o.Products)
       .WithOne(p => p.Order)

       .HasForeignKey(p => p.OrderId);

 

Perform CRUD operations using Entity Framework Core:

CRUD operations (Create, Read, Update, Delete) can be performed using methods provided by the DbContext class. For example:

Create: dbContext.Add(entity)
Read: dbContext.Set<T>().Find(id)
Update: dbContext.Update(entity)

Delete: dbContext.Remove(entity)

 

Lazy Loading: 

    Lazy loading is a feature that automatically loads related entities from the database when they are accessed for the first time. This can lead to performance issues if not used carefully.

 

Eager Loading: 

    Eager loading is the opposite of lazy loading. It explicitly loads related entities from the database along with the main entity, reducing the number of database queries needed.

 

 

DbSet:

A DbSet<T> represents a collection of TEntity entities (where TEntity is a class) that maps to a table in the database. It perform CRUD operations on the entities and supports LINQ queries to retrieve and manipulate data.

DbSet<T> is typically defined in the DbContext class. It is the primary mechanism for interacting with the database in EF Core.

 

DbContext:

The DbContext class in EF Core acts as a bridge between C# code and the database. It manages entity objects' states and provides methods for querying and saving data. Typically derived from Microsoft.EntityFrameworkCore.DbContext, it includes DbSet<TEntity> properties for each entity type.

 

Handle transactions in Entity Framework Core:

Transactions can be managed using the Transaction class provided by the DbContext. You can begin a transaction using BeginTransaction() method, and then commit or rollback the transaction as needed. For example:

Example:

   using (var transaction = dbContext.Database.BeginTransaction())
   {
       try
       {
           // Perform operations within the transaction
           dbContext.SaveChanges();
           transaction.Commit();
       }
       catch (Exception)
       {
           transaction.Rollback();
           throw;
       }

   }

 

 

Use AsQueryable() for Composition: 

  •     When building LINQ queries, start with AsQueryable() on your DbSet to allow for composition. 
  •     This enables you to add filters, sorts, and other operations before executing the query.

Example:

   var query = dbContext.Entities.AsQueryable();

   query = query.Where(e => e.SomeProperty == value);

   query = query.OrderBy(e => e.SomeProperty);

   var result = await query.ToListAsync();

 

 

Improve performance in Entity Framework Core applications:

Performance can be improved by:

Eager loading: Use Include method to load related entities upfront.

Explicit loading: Load related entities explicitly using Load method.

Avoiding N+1 queries: Use Load and ThenInclude to load related data efficiently.

Batching updates: Use SaveChanges effectively to reduce round-trips to the database

 

 

Performance considerations when working with EF Core:

To optimize performance when working with Entity Framework (EF) Core, it's important to approach the problem systematically.

1. Identify Bottlenecks and Measure

Before diving into optimization, it is essential to understand where the performance issues lie. Premature optimization can lead to wasted effort on problems that may not even exist.

  • Measure with Tools: Profiling tools like BenchmarkDotNet can be used to measure query execution times and identify slow-performing queries.
  • Logging: Enable SQL logging in EF Core to examine the generated SQL queries. This can help reveal inefficient queries and patterns that need to be optimized.
  • Database Profiler: Tools like SQL Server Profiler or EF Core's built-in logging can show the actual SQL sent to the database, helping identify areas for improvement.
  • Focus on Actual Bottlenecks: Optimization should only occur where real performance issues are detected. Look for patterns like unnecessary database round trips, slow queries, or inefficient joins.

2. Pure Database Performance

EF Core translates LINQ queries into SQL, and the performance of the application depends heavily on how efficiently the database can execute those SQL queries. Optimizing database performance is crucial for minimizing delays.

  • Indexes: Ensure that the database has appropriate indexes for frequently queried fields. This reduces the time required to find data.
  • Optimizing LINQ Queries: Some LINQ queries may generate inefficient SQL. Reviewing the SQL generated by EF Core can check for unnecessary JOINs, subqueries, or missing indexes. Methods like AsNoTracking() can be used to reduce overhead when data isn't being modified.
  • Limit Query Complexity: Complex queries involving multiple tables and relationships may cause slowdowns. These queries should be optimized by breaking them down into smaller, more manageable parts.
  • Raw SQL Queries: For complex or performance-critical queries, using raw SQL queries instead of LINQ can help fine-tune SQL execution.

3. Network Data Transfer

Transferring large volumes of data between the application and the database can cause significant performance problems, especially in distributed systems.

  • Limit Data Transfer: Use Select() in LINQ queries to load only the necessary fields. Avoid loading entire entities when only a subset of properties is needed.
  • Pagination: Implement pagination when fetching large datasets to load smaller chunks of data at a time rather than retrieving everything in one go.
  • Serialization/Deserialization: Optimize the serialization and deserialization of data when sending/receiving it between layers. Techniques like JSON compression or binary serialization can be considered for transferring large objects.
  • Caching: Cache frequently accessed data (e.g., static reference data, user profiles) to reduce repeated database round trips. EF Core supports second-level caching, but third-party solutions like EFCache can provide more fine-grained control over caching strategies.

4. Understand EF Core Internals

Understanding the internals of EF Core can lead to significant performance improvements. The more that is known about how EF Core works, the more effectively it can be utilized.

  • Change Tracking: By default, EF Core tracks changes to entities. This can introduce overhead in certain scenarios. Use AsNoTracking() for read-only queries to improve performance.
  • Query Execution: Understanding how EF Core executes queries and translates LINQ into SQL can help optimize performance. For instance, projection (Select) can reduce the number of columns retrieved, making queries faster.
  • Materialization: Materialization refers to the process of converting the data returned from the database into entity objects. Optimizing this by controlling how EF Core materializes the data can reduce unnecessary object creation (e.g., using AsEnumerable() instead of ToList() in certain cases).
  • Batching: EF Core supports batching of commands, but it can be disabled by setting EnableSensitiveDataLogging or by using specific settings to control batch size.

5. Avoid Repeated Calculations and Allocations

Repeated calculations or unnecessary allocations (e.g., creating temporary lists) can slow down the application and increase memory usage.

  • Avoid Unnecessary Calculations: Expensive calculations (e.g., Count(), Sum(), or OrderBy()) should be done on the database side when possible, rather than in memory.
  • Reuse Calculated Results: If similar calculations are needed multiple times, consider storing the result in a variable or cache rather than recalculating it each time.
  • Optimize Memory Allocation: Be mindful of excessive memory allocations, especially when working with large datasets. Process data in smaller chunks when feasible.
  • Use Object Pooling: When working with large numbers of short-lived objects, object pooling can help minimize garbage collection pressure.

6. Other Considerations

  • Use Asynchronous Methods: EF Core’s asynchronous methods (ToListAsync(), SingleOrDefaultAsync(), etc.) should be used to avoid blocking the main thread during database queries.
  • Minimize N+1 Query Problem: Use Include() strategically and avoid including too many related entities in a single query unless necessary. Consider using ThenInclude() when nested relationships need to be loaded.

Conclusion

Careful profiling, understanding EF Core's internals, and optimizing both database queries and data transfer can significantly improve application performance. Optimization should focus on areas where real bottlenecks are identified, and best practices like limiting data transfer, optimizing SQL, and reducing unnecessary calculations or memory allocations should be followed.

 

 

Use Select() for Projection: 

  • Only retrieve the data you need from the database by using Select() to specify which properties you want. 
  • This reduces the amount of data transferred from the database.

Example:

   var result = await dbContext.Entities

       .Where(e => e.SomeProperty == value)

       .OrderBy(e => e.SomeProperty)

       .Select(e => new { e.Property1, e.Property2 })

       .ToListAsync();

 

Avoid N+1 Query Problem: 

  • Be mindful of lazy loading and eager loading. 
  • Use Include() for eager loading related entities or Load() for explicit loading when needed to 
  • Avoid making multiple round-trips to the database.


Example:

   var entities = await dbContext.ParentEntities

       .Include(p => p.ChildEntities)

       .ToListAsync();

 

Use AsNoTracking() for Read-Only Queries: 

For queries where you don't intend to update entities, use AsNoTracking() to improve performance by not tracking changes.

Example:

   var entities = await dbContext.Entities

       .AsNoTracking()

       .Where(e => e.SomeProperty == value)

       .ToListAsync();

 

 

Optimize Database Updates: 

  • EF Core tracks changes automatically, but for bulk updates or inserts, consider using AddRange() or UpdateRange() 
  • to minimize round-trips.

 

Example:

   var entitiesToUpdate = await dbContext.Entities
       .Where(e => e.SomeProperty == value)
       .ToListAsync();
   
   foreach (var entity in entitiesToUpdate)
   {
       entity.SomeProperty = newValue;
   }   


   dbContext.UpdateRange(entitiesToUpdate);

   await dbContext.SaveChangesAsync();

 

Handle Transactions: 

    When performing operations that require consistency (e.g., updating multiple entities), use transactions to ensure atomicity and consistency.

Example:

   using (var transaction = await dbContext.Database.BeginTransactionAsync())

   {

       try

       {

           // Perform database operations

           await dbContext.SaveChangesAsync();

           await transaction.CommitAsync();

       }

       catch (Exception)

       {

           await transaction.RollbackAsync();

           throw;

       }

   }

 

 

Logging and Error Handling: 

                             Configure EF Core to log SQL queries and handle exceptions appropriately for better debugging and error reporting.

Example:

   protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)

   {

       optionsBuilder.UseSqlServer("connectionString")

           .LogTo(Console.WriteLine, LogLevel.Information);

   }

 

DbSet.Find(): 

    This method is used to find an entity with the given primary key values. It first looks for the entity in the context's cache, and if not found, it queries the database. If the entity is found in the context's cache, it is returned without hitting the database.

 

DbSet.SingleOrDefault(): 

    This method is used to retrieve a single entity that matches the given criteria. If no entity is found, it returns null. If multiple entities match the criteria, it throws an exception.

 

 

Entity Framework Core supports three types of inheritance mapping strategies: Table-Per-Hierarchy (TPH), Table-Per-Type (TPT), and Table-Per-Concrete-Class (TPC). You can configure entity inheritance using Fluent API or Data Annotations. For example:

Example:

    modelBuilder.Entity<BaseClass>()
        .HasDiscriminator<string>("discriminator")

        .HasValue<DerivedClass>("derived");

 

 

DbContext pooling:

    DbContext pooling is a feature introduced in Entity Framework Core 3.0 that allows DbContext instances to be reused across multiple requests. This improves performance by reducing the overhead of creating and disposing DbContext instances for each request. DbContext pooling is enabled by default in ASP.NET Core applications.

 

Complex types:

    Complex types in Entity Framework Core are non-scalar properties of an entity that are mapped to multiple columns in the database. You can define complex types using the `OwnsOne()` or `OwnsMany()` methods in Fluent API or by applying the `[Owned]` attribute to the navigation property representing the complex type.

 

AsNoTracking():

The `AsNoTracking()` method in Entity Framework Core is used to disable change tracking for a query. This can improve performance when you only need to read data and don't intend to modify or update it. By disabling change tracking, Entity Framework Core doesn't keep track of changes made to the entities returned by the query, reducing memory usage and improving performance.

 

 

Global query filters:

Global query filters allow you to define filter criteria that are applied to all queries targeting a particular entity type. These filters are defined in the `OnModelCreating` method of your `DbContext` using Fluent API. Global query filters are useful for implementing features like soft deletion, where you want to automatically exclude deleted entities from query results.

 

Owned entities are a way to map value objects (complex types) that are fully owned by an entity. They are stored within the same table as the owning entity and do not have their own identity. Owned entities are useful for modeling parts of an aggregate that don't have a standalone identity and are always accessed through their owning entity.

 

ValueConverter: Value converters are used to convert property values between CLR types and database types when reading from or writing to the database. They allow you to customize how values are stored in the database, such as converting enums to strings or encrypting sensitive data.

ValueComparer: Value comparers are used to compare property values for equality. They allow you to customize how Entity Framework Core determines whether two property values are equal, which can be useful for comparing complex types or handling case-insensitive comparisons.

 

Database connection resiliency is the ability of an application to automatically retry failed database operations in case of transient errors, such as network issues or temporary database unavailability. In Entity Framework Core, you can enable connection resiliency by configuring the DbContext options with the EnableRetryOnFailure() method, specifying the number of retry attempts and the delay between retries.

 

Seeding data:

There are several approaches to seeding data in Entity Framework Core:

    - Model Seed Data: You can define seed data directly in your entity classes using data annotations or Fluent API.

    - DbContext Seed Method: You can override the OnModelCreating method in your DbContext and use the HasData() method to seed data.

    - Custom Seeder Classes: You can create custom seeder classes that implement ISeeder interface and use them to seed data during application startup or database initialization.

 

 

Handle complex querying scenarios:

Entity Framework Core supports complex querying scenarios using LINQ. For pagination, you can use methods like Skip() and Take() to skip a certain number of records and take a specified number of records, respectively. For filtering, you can use LINQ's Where() method to apply filter criteria based on various conditions.

 

 

 

FirstOrDefault():

              Returns the first element of a sequence that matches the specified condition or null if no such

element is found. It throws an exception if more than one element matches the condition.

 

 

SingleOrDefault():

              Returns the only element of a sequence that matches the specified condition or null if no

such element exists. It throws an exception if more than one element matches the condition.

 

seeding in Entity Framework Core:

Database seeding involves populating the database with initial data when the database is created. This is useful for setting up default values, reference data, or test data. In EF Core, seeding data can be done using the `HasData` method within the `OnModelCreating` method of your `DbContext`

Example:

protected override void OnModelCreating(ModelBuilder modelBuilder) 

{      

    modelBuilder.Entity().HasData( 

     new Author { Id = 1, Name = "John Doe" }, 

     new Author { Id = 2, Name = "Jane Smith" } 

 ); 

}



Performance with large datasets can be improved by:

  • Using pagination (`Skip` and `Take`) to limit the amount of data retrieved.
  • Optimizing database indexes based on query patterns.
  • Using `AsNoTracking()` for read-only queries.
  • Considering denormalization or summary tables for frequently accessed data.
  • Monitoring and optimizing database server performance alongside EF Core optimizations. 

 

 

Add, Attach, and Update methods in Entity Framework Core:

Add: 

              Used to begin tracking a new entity that does not exist in the database yet. It will insert a new record into the database upon calling SaveChanges.

Attach: 

              Used to begin tracking an entity that already exists in the database but is not currently being tracked. It is              typically used to re-attach entities that were previously detached.

Update: 

              Used to begin tracking an entity that already exists in the database and is currently being tracked. It will mark the entity as modified and generate an update statement in the database upon calling SaveChanges.

 

 

Execute raw SQL queries in Entity Framework Core:

    EF Core allows executing raw SQL queries using the FromSqlRaw or FromSqlInterpolated methods.

 

Example:

var blogs = context.Blogs.FromSqlRaw("SELECT * FROM dbo.Blogs WHERE Url LIKE '%EF%'").ToList();

 

 

 

 

Repository pattern, and how is it implemented with Entity Framework Core:

The Repository pattern abstracts the data access logic and enables separation of concerns. In EF Core, a repository typically wraps the `DbContext` and provides methods to query, insert, update, and delete entities. 

Example:

public interface IRepository<T> where T : class

{

 IQueryable<T> GetAll();

T GetById(int id);

 void Insert(T entity);

 void Update(T entity);

 void Delete(T entity);

}

public class Repository<T> : IRepository<T> where T : class

{

 private readonly DbContext _context;

 private readonly DbSet<T> _dbSet;

 public Repository(DbContext context)

 {

 _context = context;

 _dbSet = context.Set<T>();

 }

 public IQueryable<T> GetAll()

 {

 return _dbSet;

 }

 public T GetById(int id)

 {

 return _dbSet.Find(id);

 }

 public void Insert(T entity)

 {

 _dbSet.Add(entity);

 }

public void Update(T entity)

 {

 _dbSet.Update(entity);

 }

 public void Delete(T entity)

 {

 _dbSet.Remove(entity);

 }

}

 

Handle concurrency conflicts in Entity Framework Core:

EF Core handles concurrency conflicts automatically when using optimistic concurrency control. It compares the original values of the entity with the values in the database during SaveChanges. If a conflict is detected (e.g., another user updated the record), an exception  (DbUpdateConcurrencyException) is thrown. Developers can handle this exception and decide how to resolve the conflict.

 

 

Different ways to configure entity properties in Entity Framework Core:

Entity properties can be configured using:

Data annotations: Attributes applied to properties in entity classes (e.g., [Required], [MaxLength]).

Fluent API: Method calls within the OnModelCreating method of DbContext to configure entities, properties, and relationships in a more flexible and powerful way compared to data annotations.

 

Example:

protected override void OnModelCreating(ModelBuilder modelBuilder)

{

 modelBuilder.Entity<Book>()

 .Property(b => b.Title)

 .HasMaxLength(100)

 .IsRequired();

}

 

Handle performance considerations when using EF Core in a high-traffic application:

Performance considerations in EF Core can be addressed by:

Optimizing queries: Use Include or ThenInclude to eagerly load related entities, avoiding N+1 query problems.

Using compiled queries: Compile LINQ queries for reuse across the application.

Avoiding unnecessary tracking: Use .AsNoTracking() when querying if entities are read-only.

Batching operations: Use SaveChanges sparingly and consider batching updates where possible.

Database indexes: Ensure appropriate database indexes are defined for frequently queried columns.

 

Best practices for testing EF Core applications:

Best practices for testing EF Core applications include:

Using an in-memory database: For unit tests, use DbContext configured with an in-memory database provider to avoid hitting the actual database.

Seeding data: Use data seeding to ensure consistent test data.

Mocking dependencies: Mock DbContext or repositories when testing services that depend on EF Core.

Integration tests: For testing interactions with the actual database, use integration tests with a test database that mimics the production environment.

 

Handle and log SQL generated by EF Core for debugging purposes:

EF Core can log SQL queries and commands by configuring logging in the DbContext. This is particularly useful for debugging and performance tuning. Example:

Example:

var optionsBuilder = new DbContextOptionsBuilder<MyDbContext>();

optionsBuilder.UseSqlServer(connectionString)

 .LogTo(Console.WriteLine, LogLevel.Information);


This setup will log SQL queries and other relevant information to the console.

 

Handle database transactions across multiple DbContext instances in EF Core:

Handling transactions across multiple DbContext instances in EF Core typically involves sharing the same database

connection and transaction instance. You can achieve this by passing the same DbConnection or DbTransaction

instance to multiple DbContext instances:

 

using (var connection = new SqlConnection(connectionString))

{

 await connection.OpenAsync();

 using (var transaction = connection.BeginTransaction())

 {

 try

 {

 var options1 = new DbContextOptionsBuilder<MyDbContext1>()

 .UseSqlServer(connection)

 .Options;

 var context1 = new MyDbContext1(options1);

 var options2 = new DbContextOptionsBuilder<MyDbContext2>()

 .UseSqlServer(connection)

 .Options;

 var context2 = new MyDbContext2(options2);

 // Use context1 and context2 within the same transaction

 // ...

 await context1.SaveChangesAsync();

 await context2.SaveChangesAsync();

 transaction.Commit();

 }

 catch (Exception)

 {

 transaction.Rollback();

 throw;

 }

 }

}

 

 

 

 

Map a database view to an entity in EF Core:

Mapping a database view to an entity in EF Core involves defining an entity class that corresponds to the view and

configuring it in the OnModelCreating method using the ToView method:

 

public class BlogView

{

 public int BlogId { get; set; }

 public string Url { get; set; }

}

protected override void OnModelCreating(ModelBuilder modelBuilder)

{

 modelBuilder.Entity<BlogView>()

 .ToView("BlogView")

 .HasKey(b => b.BlogId);

}

 

BlogView is mapped to the BlogView database view.

 

 

 

 

s EF Core handle migrations in a continuous integration/continuous deployment (CI/CD) pipeline?

 

In a CI/CD pipeline, EF Core migrations can be automated using commands like dotnet ef migrations add and

dotnet ef database update. Here are steps typically involved:

Developers create and test migrations locally.

Migrations are added to version control.

CI/CD pipeline runs migrations against the database during deployment.

Automated tests verify database schema changes.

 

 

Owned Entities in Entity Framework Core.

 

Owned Entities in EF Core are a way to represent part of an entity's state as owned by the entity itself rather than

being a standalone entity with its own identity. They are typically used to model complex value objects that are

logically part of the owning entity. Here's an example:

Example:

public class Address

{

 public string Street { get; set; }

 public string City { get; set; }

 public string ZipCode { get; set; }

}

public class Customer

{

 public int CustomerId { get; set; }

 public string Name { get; set; }

 public Address ShippingAddress { get; set; } // Owned entity

 public Address BillingAddress { get; set; } // Owned entity

}

 

 

 

Configure EF Core to handle database transactions with distributed transactions (e.g., across

multiple databases):

Handling distributed transactions in EF Core involves using a distributed transaction coordinator (DTC) supported by the underlying database provider (e.g., MSDTC for SQL Server). Here's an example of coordinating transactions across multiple databases:

Example:

using (var scope = new TransactionScope(TransactionScopeOption.Required, new TransactionOptions

{

 IsolationLevel = IsolationLevel.ReadCommitted,

Timeout = TransactionManager.MaximumTimeout

}))

{

 try

 {

 // Perform operations across multiple databases using EF Core contexts

 context1.SaveChanges();

 context2.SaveChanges();

 scope.Complete(); // Commit transaction

 }

 catch (Exception)

 {

 // Handle exceptions and rollback if necessary

 }

 }

 

 

 

Implement a custom database function or computed column in Entity Framework Core:

EF Core allows defining custom database functions or computed columns using the HasDbFunction method in OnModelCreating. For example, defining a custom database function:

Example:

[DbFunction("dbo", "CalculateTax")]

public static decimal CalculateTax(decimal amount, decimal taxRate)

{

 throw new NotImplementedException();

}

protected override void OnModelCreating(ModelBuilder modelBuilder)

{

 modelBuilder.HasDbFunction(typeof(MyDbContext).GetMethod(nameof(CalculateTax)));

}

 

 

Limitations or considerations when using EF Core in a microservices architecture:

In a microservices architecture, using EF Core introduces challenges such as:

Database schema management: Each microservice might have its own database and schema, making it challenging to manage schema changes. Performance: EF Core's overhead might be higher compared to lightweight data access methods suited for microservices. Data consistency: Distributed transactions and eventual consistency must be carefully managed.

Comments

Popular posts from this blog

Multiline to singleline IN C# - CODING

EF Core interview questions for beginners

EF Core interview questions for experienced