Some Best Practices for Entity Framework Core, my opinion

March 19, 2025 By Joao LIVIO

Entity Framework Core (EF Core) is a powerful ORM (Object-Relational Mapper) for .NET applications that simplifies database interactions. However, to build efficient, scalable, and maintainable applications, following best practices is crucial.

In this blog post, we’ll explore best practices for EF Core, covering topics like:

  • Primary Keys (PK) & Foreign Keys (FK)
  • Annotations & Fluent API
  • Inverse Properties for Navigation
  • Optimizing Performance for Queries
  • Best Security Practices for APIs

Defining Primary and Foreign Keys

A well-structured database starts with proper primary and foreign key definitions.

Primary Key (PK) Best Practices

  • Always use a unique identifier (int, GUID, etc.).
  • Use Key annotation or Fluent API to explicitly mark the PK.
  • Prefer auto-incremented IDs (Identity in SQL Server, Serial in PostgreSQL).

Example: Defining a Primary Key

public class Product
{
    [Key] // Explicitly marking as PK
    public int Id { get; set; }
    
    [Required]
    [MaxLength(100)]
    public string Name { get; set; }
}

Foreign Key (FK) Relationships

Foreign Keys define relationships between entities and ensure referential integrity.

Example: Defining a Foreign Key

public class Order
{
    [Key]
    public int Id { get; set; }

    [Required]
    public int ProductId { get; set; } // Foreign Key to Product

    [ForeignKey("ProductId")] // Explicitly defining FK
    public Product? Product { get; set; }
}

Best Practice: Always use [ForeignKey] or Fluent API to explicitly define relationships.

Using Annotations for Data Validation

EF Core provides Data Annotations to enforce constraints on your entities.

Example: Using Data Annotations

public class Customer
{
    [Key]
    public int Id { get; set; }
    
    [Required] // Ensures Name is NOT NULL
    [MaxLength(200)] // Limits Name to 200 chars
    public string Name { get; set; }

    [EmailAddress] // Ensures valid email format
    public string Email { get; set; }
}

Best Practice: Always use annotations like [Required], [MaxLength], [EmailAddress] to enforce constraints at the database level.

Inverse Properties & Navigation Properties

Why Use Inverse Properties?

EF Core automatically determines relationships, but explicitly defining Inverse Properties improves performance and readability.

Why Use ICollection<T> Instead of List<T>?

  • ICollection<T> is more flexible than List<T> because EF Core handles lazy loading more efficiently.
  • When defining navigation properties, using ICollection<T> ensures better proxy support for EF Core.
  • List<T> has a concrete implementation, which can cause unintended behavior when EF Core proxies are generated dynamically.

Example: One-to-Many Relationship with Inverse Property

public class Customer
{
    [Key]
    public int Id { get; set; }
    public string Name { get; set; }
    
    // One Customer can have multiple Orders
    [InverseProperty("Customer")] 
    public ICollection<Order> Orders { get; set; } = new HashSet<Order>();
}

public class Order
{
    [Key]
    public int Id { get; set; }
    public int CustomerId { get; set; }
    
    [ForeignKey("CustomerId")]
    public Customer Customer { get; set; } // Navigation Property
}

Best Practice: Always use [InverseProperty] when there are multiple relationships between the same entities to avoid ambiguity.

Optimizing Query Performance in EF Core

Handling database queries efficiently is crucial for high-performance APIs.

Best Practices for Query Performance

Use AsNoTracking() for Read-Only Queries

var products = dbContext.Products.AsNoTracking().ToList();

Why?

  • Improves performance by disabling change tracking.
  • Ideal for read-heavy operations.

Use Eager Loading to Avoid N+1 Queries

var customerNames = dbContext.Customers.Select(c => new { c.Id, c.Name }).ToList();

Why?

  • Reduces unnecessary data loading.
  • Improves API response times.

Securing EF Core APIs

Security is critical when exposing database interactions via an API. Here are the best practices:

Secure API Endpoints with Authentication & Authorization

Use JWT (JSON Web Tokens) or OAuth 2.0 to authenticate users before accessing EF Core.

[Authorize]
[HttpGet("orders")]
public IActionResult GetOrders()
{
    var orders = _dbContext.Orders.Include(o => o.Customer).ToList();
    return Ok(orders);
}

Why?

  • Prevents unauthorized access.
  • Ensures only authenticated users can retrieve data.

Prevent SQL Injection with Parameterized Queries

EF Core already prevents SQL injection, but never concatenate user input in queries.

var customer = _dbContext.Customers.Where(c => c.Email == userInput).FirstOrDefault();

Why?

  • Protects against malicious SQL injection attacks.
  • Ensures data integrity.

Implement Rate Limiting to Prevent DDoS Attacks

Use ASP.NET Rate Limiting Middleware to protect EF Core queries from excessive requests.

services.AddRateLimiter(options =>
{
    options.GlobalLimiter = RateLimitPartition.GetFixedWindowLimiter(
        partitionKey: context => context.Connection.RemoteIpAddress.ToString(),
        factory: _ => new FixedWindowRateLimiterOptions
        {
            PermitLimit = 10,
            Window = TimeSpan.FromSeconds(60)
        });
});

Why?

  • Prevents API abuse by limiting requests per user.
  • Enhances overall system stability.

Conclusion

Following EF Core best practices ensures your application is scalable, maintainable, and performant.

Key Takeaways:

  • Use proper PKs & FKs for relationships.
  • Use annotations to enforce validation.
  • Optimize queries using AsNoTracking(), Include(), and Select().
  • Secure APIs with authentication, rate limiting, and best security practices.