Entity Framework Core Configuration

This document covers the various ways you can configure Entity Framework Core (EF Core) to suit your application's needs. Proper configuration is crucial for performance, security, and maintainability.

Connection Strings

The connection string tells EF Core how to connect to your database. It typically includes the server name, database name, authentication details, and other relevant parameters.

ASP.NET Core Configuration

In ASP.NET Core applications, connection strings are commonly stored in the appsettings.json file and accessed via the configuration system.

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=MyDatabase;Trusted_Connection=True;MultipleActiveResultSets=true"
  }
}

You can then inject IConfiguration into your DbContext constructor to retrieve the connection string.

public class ApplicationDbContext : DbContext
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) : base(options) { }

    // ... DbSets
}

// In Startup.cs (or Program.cs for .NET 6+)
services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

Other Application Types

For non-web applications, you can also store connection strings in configuration files or directly in code (though the former is generally preferred).

Options Configuration

EF Core provides extensive options for configuring its behavior. These are typically set when configuring your DbContext.

DbContextOptionsBuilder

The DbContextOptionsBuilder class allows you to set various options:

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
    if (!optionsBuilder.IsConfigured)
    {
        optionsBuilder.UseSqlServer("Server=myServer;Database=myDatabase;Trusted_Connection=True;")
                      .EnableSensitiveDataLogging() // For development
                      .UseQueryTrackingBehavior(QueryTrackingBehavior.NoTracking); // Example: disable change tracking
    }
}
Note: EnableSensitiveDataLogging() should only be used during development as it can expose sensitive data in logs.

Conventions

Conventions are a set of rules EF Core uses to infer model mappings from your entity types and properties without explicit configuration. You can customize or extend these conventions.

Default Conventions

EF Core has many built-in conventions, such as:

Custom Conventions

You can create custom conventions to enforce specific naming patterns, data types, or other model aspects.

public class MyConvention : IModelFinalizingConvention
{
    public void Apply(IConventionModelBuilder modelBuilder)
    {
        // Example: Ensure all string properties are NVARCHAR(200)
        foreach (var property in modelBuilder.Metadata.GetProperties())
        {
            if (property.ClrType == typeof(string))
            {
                property.SetMaxLength(200);
            }
        }
    }
}

// In DbContext.OnModelCreating
protected override void ConfigureConventions(ModelConfigurationBuilder configurationBuilder)
{
    configurationBuilder.Conventions.Add(<MyConvention>);
}

Fluent API

The Fluent API provides a programmatic way to configure your EF Core model. It's generally preferred over Data Annotations for complex configurations as it keeps mapping logic separate from your entity classes.

Configuring in OnModelCreating

The OnModelCreating method in your DbContext is where you typically use the Fluent API.

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Product>().HasKey(p => p.ProductId); // Configure primary key

    modelBuilder.Entity<Product>()
        .Property(p => p.Name)
        .IsRequired() // Make Name non-nullable
        .HasMaxLength(100); // Set max length

    modelBuilder.Entity<Category>()
        .HasMany(c => c.Products) // Configure one-to-many relationship
        .WithOne(p => p.Category)
        .HasForeignKey(p => p.CategoryId);

    modelBuilder.Entity<Order>()
        .ToTable("Orders") // Map to a specific table name
        .HasIndex(o => o.OrderDate); // Add an index

    // Configure relationships with shared primary keys
    modelBuilder.Entity<Blog>().HasMany(b => b.Posts).WithOne(p => p.Blog).HasForeignKey(p => p.BlogId);
    modelBuilder.Entity<Post>().HasOne(p => p.Blog).WithMany(b => b.Posts).HasForeignKey(p => p.BlogId);
}
Tip: The Fluent API offers a rich set of configurations for relationships, keys, indexes, table names, column types, and more.

Data Annotations

Data Annotations are attributes you can apply directly to your entity classes to define their mapping and constraints. They are simpler for basic configurations.

Common Data Annotations

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

public class Product
{
    [Key]
    public int ProductId { get; set; }

    [Required]
    [StringLength(100)]
    public string Name { get; set; }

    [Column(TypeName = "decimal(18, 2)")]
    public decimal Price { get; set; }

    public int CategoryId { get; set; }
    [ForeignKey("CategoryId")]
    public virtual Category Category { get; set; }
}

[Table("ProductCategories")]
public class Category
{
    [Key]
    public int CategoryId { get; set; }
    public string Description { get; set; }

    public virtual ICollection<Product> Products { get; set; }
}
Important: When using both Fluent API and Data Annotations for the same property, the Fluent API configuration will generally override Data Annotations.

Understanding these configuration methods allows you to tailor EF Core's behavior to your specific database schema and application requirements.