Skip to content

实体构造函数与属性初始化 ​

概述 ​

EF Core 在实例化实体时支持多种构造方式,理解这些机制对于正确初始化实体、实现领域驱动设计(DDD)和控制对象生命周期至关重要。

核心概念 ​

概念说明
无参构造函数EF Core 默认使用无参构造函数创建实体
带参构造函数EF Core 6+ 支持使用带参构造函数
属性初始化器C# 属性初始化语法
字段支持EF Core 可以直接写入私有字段
工厂方法静态工厂方法创建实体

无参构造函数 ​

默认行为 ​

csharp
// EF Core 默认使用无参构造函数
public class Product
{
    // ✅ EF Core 会自动生成无参构造函数(如果没有定义任何构造函数)
    public int Id { get; set; }
    public string Name { get; set; } = null!;
    public decimal Price { get; set; }
}

// 等价于:
public class Product
{
    public Product()  // 编译器自动生成
    {
    }
    
    public int Id { get; set; }
    public string Name { get; set; } = null!;
    public decimal Price { get; set; }
}

显式定义无参构造函数 ​

csharp
public class Product
{
    // ⚠️ 一旦定义了带参构造函数,必须显式定义无参构造函数
    private Product()
    {
        // EF Core 使用
    }
    
    // 业务代码使用的构造函数
    public Product(string name, decimal price)
    {
        Name = name ?? throw new ArgumentNullException(nameof(name));
        Price = price;
    }
    
    public int Id { get; set; }
    public string Name { get; private set; }
    public decimal Price { get; private set; }
}

带参构造函数(EF Core 6+) ​

基础用法 ​

csharp
// EF Core 6+ 支持使用带参构造函数
public class Order
{
    // ✅ EF Core 会匹配参数名与属性名
    public Order(int id, string customerEmail, DateTime orderDate)
    {
        Id = id;
        CustomerEmail = customerEmail;
        OrderDate = orderDate;
    }
    
    public int Id { get; private set; }
    public string CustomerEmail { get; private set; }
    public DateTime OrderDate { get; private set; }
    public OrderStatus Status { get; private set; }
}

// 配置
modelBuilder.Entity<Order>(entity =>
{
    entity.HasKey(o => o.Id);
    // EF Core 自动识别构造函数
});

参数匹配规则 ​

csharp
public class Customer
{
    // EF Core 按以下顺序匹配:
    // 1. 参数名与属性名完全匹配(不区分大小写)
    // 2. 参数名去掉下划线后与属性名匹配
    
    public Customer(int id, string firstName, string lastName, string email)
    {
        Id = id;
        FirstName = firstName;
        LastName = lastName;
        Email = email;
    }
    
    public int Id { get; private set; }
    public string FirstName { get; private set; }
    public string LastName { get; private set; }
    public string Email { get; private set; }
}

// 也支持这种命名:
public Customer(int id, string first_name, string last_name, string email_address)
{
    // EF Core 会匹配到 FirstName, LastName, EmailAddress
}

部分参数 + 属性设置 ​

csharp
public class Article
{
    // 构造函数设置必需属性
    public Article(string title, string content)
    {
        Title = title ?? throw new ArgumentNullException(nameof(title));
        Content = content ?? throw new ArgumentNullException(nameof(content));
        CreatedAt = DateTime.UtcNow;
        Status = ArticleStatus.Draft;
    }
    
    // EF Core 通过属性设置器设置其他属性
    public int Id { get; private set; }
    public string Title { get; private set; }
    public string Content { get; private set; }
    public DateTime CreatedAt { get; private set; }
    public DateTime? PublishedAt { get; set; }  // EF Core 直接设置
    public ArticleStatus Status { get; private set; }
}

字段支持(Backing Fields) ​

直接写入字段 ​

csharp
public class Blog
{
    private string _url = null!;  //  backing field
    
    public int Id { get; set; }
    
    // EF Core 可以直接写入 _url 字段
    public string Url
    {
        get => _url;
        private set => _url = value ?? throw new ArgumentNullException(nameof(value));
    }
}

// 配置
modelBuilder.Entity<Blog>(entity =>
{
    entity.Property(b => b.Url)
          .HasField("_url")  // 指定 backing field
          .UsePropertyAccessMode(PropertyAccessMode.Field);
});

自动识别 Backing Field ​

csharp
public class Product
{
    private decimal _price;  // EF Core 自动识别为 Price 的 backing field
    
    public int Id { get; set; }
    
    public decimal Price
    {
        get => _price;
        set
        {
            if (value < 0)
                throw new ArgumentException("Price cannot be negative");
            _price = value;
        }
    }
}

// EF Core 自动识别以下命名约定:
// - _price, _Price, m_price, m_Price
// - price_, Price_

只读集合字段 ​

csharp
public class Order
{
    private readonly List<OrderItem> _items = new();
    
    public int Id { get; set; }
    
    // EF Core 写入 _items 字段
    public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
    
    // 业务方法添加订单项
    public void AddItem(Product product, int quantity)
    {
        _items.Add(new OrderItem(product, quantity));
    }
}

// 配置
modelBuilder.Entity<Order>(entity =>
{
    entity.HasMany(o => o.Items)
          .WithOne()
          .HasForeignKey("OrderId")
          .OnDelete(DeleteBehavior.Cascade);
});

属性初始化器 ​

C# 属性初始化 ​

csharp
public class User
{
    public int Id { get; set; }
    
    // ✅ 属性初始化器
    public string Username { get; set; } = string.Empty;
    public bool IsActive { get; set; } = true;
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
    public List<string> Roles { get; set; } = new();
}

// EF Core 行为:
// 1. 调用构造函数(如果有)
// 2. 执行属性初始化器
// 3. 从数据库加载值覆盖属性

注意事项 ​

csharp
public class Product
{
    // ⚠️ 注意: 每次 new Product() 都会创建新的 List 实例
    public List<Tag> Tags { get; set; } = new();
    
    // ✅ 更好的做法: 使用构造函数初始化
    public Product()
    {
        Tags = new();
    }
}

工厂方法模式 ​

静态工厂方法 ​

csharp
public class Money
{
    private Money(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency;
    }
    
    public static Money FromDecimal(decimal amount, string currency)
    {
        return new Money(amount, currency);
    }
    
    public static Money Zero(string currency)
    {
        return new Money(0, currency);
    }
    
    public decimal Amount { get; private set; }
    public string Currency { get; private set; }
}

// EF Core 配置
modelBuilder.ComplexType<Money>(money =>
{
    money.Property(m => m.Amount).HasColumnName("Amount");
    money.Property(m => m.Currency).HasColumnName("Currency");
});

工厂方法创建聚合根 ​

csharp
public class Order : AggregateRoot
{
    private Order()
    {
        // EF Core 使用
    }
    
    public static Order Create(string customerId, List<OrderItemRequest> items)
    {
        var order = new Order
        {
            Id = Guid.NewGuid(),
            CustomerId = customerId,
            OrderDate = DateTime.UtcNow,
            Status = OrderStatus.Pending
        };
        
        foreach (var item in items)
        {
            order.AddItem(item.ProductId, item.Quantity, item.UnitPrice);
        }
        
        // 发布领域事件
        order.AddDomainEvent(new OrderCreatedEvent(order.Id, customerId));
        
        return order;
    }
    
    private void AddItem(int productId, int quantity, decimal unitPrice)
    {
        _items.Add(new OrderItem(productId, quantity, unitPrice));
        RecalculateTotal();
    }
    
    public Guid Id { get; private set; }
    public string CustomerId { get; private set; }
    public DateTime OrderDate { get; private set; }
    public OrderStatus Status { get; private set; }
    private readonly List<OrderItem> _items = new();
    public IReadOnlyCollection<OrderItem> Items => _items.AsReadOnly();
    public decimal TotalAmount { get; private set; }
}

DDD 实体构造函数最佳实践 ​

保护不变量 ​

csharp
public class EmailAddress
{
    private EmailAddress(string value)
    {
        if (!IsValidEmail(value))
            throw new ArgumentException("Invalid email format");
        
        Value = value.ToLowerInvariant();
    }
    
    public static EmailAddress Create(string email)
    {
        return new EmailAddress(email);
    }
    
    public string Value { get; private set; }
    
    private static bool IsValidEmail(string email)
    {
        // 邮箱验证逻辑
        return !string.IsNullOrWhiteSpace(email) && 
               email.Contains("@") && 
               email.Contains(".");
    }
}

// EF Core 配置
modelBuilder.ComplexType<EmailAddress>(email =>
{
    email.Property(e => e.Value).HasColumnName("Email");
});

值对象构造函数 ​

csharp
public record Address
{
    public Address(string street, string city, string zipCode)
    {
        Street = street ?? throw new ArgumentNullException(nameof(street));
        City = city ?? throw new ArgumentNullException(nameof(city));
        ZipCode = zipCode ?? throw new ArgumentNullException(nameof(zipCode));
    }
    
    public string Street { get; init; }
    public string City { get; init; }
    public string ZipCode { get; init; }
}

// EF Core 8+ JSON 列
modelBuilder.Entity<Customer>(entity =>
{
    entity.OwnsOne(c => c.Address, address =>
    {
        address.ToJson();
    });
});

构造函数注入(非 EF Core 场景) ​

DbContext 构造函数注入 ​

csharp
public class ProductService
{
    private readonly AppDbContext _context;
    
    // ✅ 依赖注入
    public ProductService(AppDbContext context)
    {
        _context = context;
    }
    
    public async Task<Product?> GetByIdAsync(int id)
    {
        return await _context.Products.FindAsync(id);
    }
}

❌ 不要在实体中注入服务 ​

csharp
// ❌ 错误: 实体不应该有服务依赖
public class Order
{
    private readonly IPaymentGateway _paymentGateway;  // ⚠️ 不要这样做
    
    public Order(IPaymentGateway paymentGateway)
    {
        _paymentGateway = paymentGateway;
    }
}

// ✅ 正确: 在服务层处理外部依赖
public class OrderService
{
    private readonly AppDbContext _context;
    private readonly IPaymentGateway _paymentGateway;
    
    public OrderService(AppDbContext context, IPaymentGateway paymentGateway)
    {
        _context = context;
        _paymentGateway = paymentGateway;
    }
    
    public async Task PayOrderAsync(int orderId)
    {
        var order = await _context.Orders.FindAsync(orderId);
        await _paymentGateway.Charge(order.TotalAmount);
        order.Status = OrderStatus.Paid;
        await _context.SaveChangesAsync();
    }
}

性能考虑 ​

构造函数 vs 属性设置 ​

csharp
[Benchmark]
public void WithConstructor()
{
    var product = new Product("Laptop", 999.99m);
}

[Benchmark]
public void WithProperties()
{
    var product = new Product();
    product.Name = "Laptop";
    product.Price = 999.99m;
}

// 结果:
// | Method         | Mean     | Allocated |
// |--------------- |---------:|----------:|
// | WithConstructor| 5.2 ns   | 48 B      |
// | WithProperties | 8.7 ns   | 48 B      |
// 差异很小,选择可读性更好的方式

避免构造函数中的复杂逻辑 ​

csharp
// ❌ 错误: 构造函数中执行耗时操作
public class Product
{
    public Product(int categoryId)
    {
        // ⚠️ 不要在构造函数中查询数据库
        Category = dbContext.Categories.Find(categoryId);
    }
}

// ✅ 正确: 延迟加载或使用工厂
public class Product
{
    private Product()
    {
    }
    
    public static async Task<Product> CreateAsync(
        AppDbContext context, 
        int categoryId)
    {
        var product = new Product();
        product.Category = await context.Categories.FindAsync(categoryId);
        return product;
    }
}

最佳实践 ​

✅ 推荐做法 ​

1. 使用私有构造函数保护不变量 ​

csharp
public class Money
{
    private Money(decimal amount)
    {
        if (amount < 0)
            throw new ArgumentException("Amount cannot be negative");
        Amount = amount;
    }
    
    public static Money FromDecimal(decimal amount)
    {
        return new Money(amount);
    }
    
    public decimal Amount { get; private set; }
}

2. 优先使用不可变属性 ​

csharp
// EF Core 6+
public class Product
{
    public Product(string name, decimal price)
    {
        Name = name;
        Price = price;
    }
    
    public int Id { get; private set; }
    public string Name { get; private set; }  // init-only
    public decimal Price { get; private set; }
}

3. 明确 EF Core 使用的构造函数 ​

csharp
public class Order
{
    // 标记为 EF Core 使用
    private Order()
    {
        // EF Core only
    }
    
    // 业务代码使用
    public Order(string customerId)
    {
        CustomerId = customerId;
        OrderDate = DateTime.UtcNow;
    }
}

❌ 避免的错误 ​

1. 不要在构造函数中执行业务逻辑 ​

csharp
// ❌ 错误
public class Order
{
    public Order(string customerId)
    {
        // ⚠️ 不要发送电子邮件
        _emailService.SendConfirmation(customerId);
    }
}

// ✅ 正确: 在 service 层处理
public class OrderService
{
    public async Task<Order> CreateOrderAsync(string customerId)
    {
        var order = new Order(customerId);
        _context.Orders.Add(order);
        await _context.SaveChangesAsync();
        
        await _emailService.SendConfirmation(customerId);
        return order;
    }
}

2. 不要忘记无参构造函数 ​

csharp
// ❌ 错误: 定义了带参构造函数但没有无参构造函数
public class Product
{
    public Product(string name)  // ⚠️ EF Core 无法实例化
    {
        Name = name;
    }
}

// ✅ 正确: 添加私有无参构造函数
public class Product
{
    private Product()  // EF Core 使用
    {
    }
    
    public Product(string name)  // 业务代码使用
    {
        Name = name;
    }
}

总结 ​

构造函数选择决策树 ​

需要创建实体?
│
├─ EF Core 从数据库加载?
│  ├─ 简单实体 → ✅ 无参构造函数
│  └─ 复杂实体 → ✅ 带参构造函数(EF Core 6+)
│
├─ 业务代码创建?
│  ├─ 需要验证 → ✅ 静态工厂方法
│  └─ 简单创建 → ✅ 公共构造函数
│
└─ DDD 聚合根?
   └─ ✅ 私有构造函数 + 静态工厂方法

核心要点 ​

  1. 无参构造函数: EF Core 默认使用,必要时设为 private
  2. 带参构造函数: EF Core 6+ 支持,参数名匹配属性名
  3. Backing Fields: EF Core 可直接写入私有字段
  4. 工厂方法: 用于复杂创建逻辑和验证
  5. 不可变性: 优先使用 private set 或 init
  6. 避免副作用: 构造函数中不要执行 IO 操作
  7. DDD 实践: 私有构造函数 + 静态工厂保护不变量

基于 MIT 许可发布